コンテンツにスキップ

Guanceログ収集分析のベストプラクティス


ログ収集

まず、ログとは何でしょうか。ログとは、プログラムによって生成され、一定の形式(通常はタイムスタンプを含む)に従ったテキストデータです。
通常、ログはサーバーによって生成され、さまざまなファイルに出力されます。一般的には、システムログ、アプリケーションログ、セキュリティログがあります。これらのログは、さまざまなマシンに分散して保存されています。システムに障害が発生した場合、エンジニアは各サーバーにログインし、grep / sed / awk などの Linux スクリプトツールを使用して、ログから障害の原因を調査する必要があります。ログシステムがない場合、まずリクエストを処理したサーバーを特定する必要があります。このサーバーに複数のインスタンスがデプロイされている場合は、各アプリケーションインスタンスのログディレクトリに移動してログファイルを探す必要があります。各アプリケーションインスタンスには、ログローテーションポリシー(例:毎日1つのファイルを生成する、またはログファイルが特定のサイズに達したら新しいファイルを生成する)や、ログの圧縮アーカイブポリシーなども設定されています。
この一連の流れは、障害の調査や障害原因の早期発見に大きな手間をかけています。したがって、これらのログを一元管理し、集中検索機能を提供できれば、診断効率を向上させるだけでなく、システム状況を包括的に把握し、事後対応の受け身の状態を回避できます。
そのため、ログデータは以下の点で非常に重要な役割を果たします。

  • データ検索:ログ情報を検索することで、該当するバグを特定し、解決策を見つけます。
  • サービス診断:ログ情報を統計・分析することで、サーバーの負荷やサービス動作状態を把握します。
  • データ分析:さらに詳細なデータ分析を行うことができます。

ファイルログの収集

このドキュメントでは、Nginx ログ収集を例に説明します。DataKit インストールディレクトリの conf.d/log ディレクトリに移動し、logging.conf.sample をコピーして logging.conf という名前で保存します。例は以下の通りです。

[[inputs.logging]]
  # 日志文件列表,可以指定绝对路径,支持使用 glob 规则进行批量指定
  # 推荐使用绝对路径
  logfiles = [
    "/var/log/nginx/access.log",                         
    "/var/log/nginx/error.log",                      
  ]

  # 文件路径过滤,使用 glob 规则,符合任意一条过滤条件将不会对该文件进行采集
  ignore = [""]

  # 数据来源,如果为空,则默认使用 'default'
  source = ""

  # 新增标记tag,如果为空,则默认使用 $source
  service = ""

  # pipeline 脚本路径,如果为空将使用 $source.p,如果 $source.p 不存在将不使用 pipeline
  pipeline = "nginx.p"

  # 过滤对应 status:
  #   `emerg`,`alert`,`critical`,`error`,`warning`,`info`,`debug`,`OK`
  ignore_status = []

  # 选择编码,如果编码有误会导致数据无法查看。默认为空即可:
  #    `utf-8`, `utf-16le`, `utf-16le`, `gbk`, `gb18030` or ""
  character_encoding = ""

  ## 设置正则表达式,例如 ^\d{4}-\d{2}-\d{2} 行首匹配 YYYY-MM-DD 时间格式
  ## 符合此正则匹配的数据,将被认定为有效数据,否则会累积追加到上一条有效数据的末尾
  ## 使用3个单引号 '''this-regexp''' 避免转义
  ## 正则表达式链接:https://golang.org/pkg/regexp/syntax/#hdr-Syntax
  # multiline_match = '''^\S'''

  ## 是否删除 ANSI 转义码,例如标准输出的文本颜色等
  remove_ansi_escape_codes = false

  # 自定义 tags
  [inputs.logging.tags]
   app = oa

カスタムタグは任意の key-value 値を設定できます。 ●設定完了後、すべてのメトリクスに app = oa というタグが付与され、高速なクエリが可能になります。 ●関連ドキュメント < DataFlux Tag 应用最佳实践> Datakit を再起動します。

systemctl restart datakit

マルチラインログの収集

マルチラインログの最初の行の特徴を認識することで、あるログ行が新しいログかどうかを判断できます。この特徴に一致しない場合、現在の行は前のマルチラインログの追記と見なします。 例を挙げると、通常ログは先頭から記述されますが、プログラムクラッシュ時のコールスタックログのように、先頭から記述されないログテキストもあります。このようなログテキストは、マルチラインログです。DataKit では、正規表現を使用してマルチラインログの特徴を認識します。正規表現に一致するログ行が新しいログの始まりとなり、それ以降の一致しないすべてのログ行は、この新しいログの追記と見なされ、別の正規表現に一致する新しいログ行が現れるまで続きます。 マルチラインログ収集を有効にするには、logging.conf で以下の設定を変更する必要があります。

match = '''这里填写具体的正则表达式''' # 注意,这里的正则俩边,建议分别加上三个「英文单引号」

ログ収集で使用される正規表現のスタイルについてはこちらを参照してください。

以下に Python ログの例を示します。

2020-10-23 06:41:56,688 INFO demo.py 1.0
2020-10-23 06:54:20,164 ERROR /usr/local/lib/python3.6/dist-packages/flask/app.py Exception on /0 [GET]
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
ZeroDivisionError: division by zero
2020-10-23 06:41:56,688 INFO demo.py 5.0

Match 設定は ^\d{4}-\d{2}-\d{2}.*(つまり、2020-10-23 のような行頭に一致するもの)とします。

ログの特殊バイトコードフィルタリング

ログには読み取り不可能なバイトコード(ターミナル出力の色など)が含まれる場合があります。logging.confremove_ansi_escape_codestrue に設定することで、これらを削除してフィルタリングできます。

この機能を有効にすると、処理時間がわずかに増加します。

リモートログファイルの収集

Linux システムでは、NFS 方式を使用して、ログが存在するホストのファイルパスを DataKit がインストールされたホストにマウントし、Logging 収集で対応するログパスを設定することで収集を完了できます。

ストリーミングログの収集

このドキュメントでは、Fluentd ログの収集を例に説明します。

例の Fluentd バージョンは td-agent-4.2.x です。各バージョンによって設定が異なる場合があります。

DataKit 設定

ストリーミングログを収集する際、DataKit は HTTP Server を起動し、ログテキストデータを受信して Guance に送信します。HTTP URL は固定で /v1/write/logstreaming、つまり http://Datakit_IP:PORT/v1/write/logstreaming です。

注:DataKit が Kubernetes に DaemonSet としてデプロイされている場合、Service 経由でアクセスできます。アドレスは http://datakit-service.datakit:9529 です。

DataKit インストールディレクトリの conf.d/log ディレクトリに移動し、logstreaming.conf.sample をコピーして logstreaming.conf という名前で保存します。例は以下の通りです。

[inputs.logstreaming]
  ignore_url_tags = true

DataKit を再起動します。

systemctl restart datakit
パラメータサポート

Logstreaming は HTTP URL にパラメータを追加して、ログデータを操作することをサポートしています。パラメータは以下の通りです。

  • type:データ形式。現在は influxdb のみをサポートしています。
  • type が influxdb の場合(/v1/write/logstreaming?type=influxdb)、データ自体が行プロトコル形式(デフォルトの precision は s)であることを示し、組み込みのタグのみを追加し、他の操作は行いません。
  • この値が空の場合、データの分割や pipeline などの処理が行われます。
  • source:データソースを示します。行プロトコルの measurement に相当します。例:nginx や redis(/v1/write/logstreaming?source=nginx
  • typeinfluxdb の場合、この値は無効です。
  • デフォルトは default です。
  • service:service タグフィールドを追加します。例:(/v1/write/logstreaming?service=nginx_service
  • デフォルトは source パラメータ値です。
  • pipeline:データで使用する Pipeline 名を指定します。例:nginx.p/v1/write/logstreaming?pipeline=nginx.p

Fluentd 設定

Fluentd が Nginx ログを収集し、上位のサーバーに転送する Plugin 設定を例にします。サーバーに直接送信して処理するのではなく、DataKit で処理して Guance プラットフォームに送信して分析したい場合です。

##pc端日志收集
<source>
  @type tail
  format ltsv
  path /var/log/nginx/access.log
  pos_file /var/log/buffer/posfile/access.log.pos
  tag nginx
  time_key time
  time_format %d/%b/%Y:%H:%M:%S %z
</source>

## 收集的数据由tcp协议转发到多个server的49875端口

<match nginx>
 type forward
  <server>
   name es01
   host es01
   port 49875
   weight 60
  </server>
  <server>
   name es02
   host es02
   port 49875
   weight 60
  </server>
</match>
Match の Output を変更し、タイプを Http に指定し、Endpoint を Logstreaming が有効な DataKit アドレスに向けることで収集が完了します。

##pc端日志收集
<source>
  @type tail
  format ltsv
  path /var/log/nginx/access.log
  pos_file /var/log/buffer/posfile/access.log.pos
  tag nginx
  time_key time
  time_format %d/%b/%Y:%H:%M:%S %z
</source>

##收集的数据由http协议转发至本地 DataKit

## nginx output

<match nginx>
  @type http
  endpoint http://127.0.0.1:9529/v1/write/logstreaming?source=nginx_td&pipeline=nginx.p
  open_timeout 2
  <format>
    @type json
  </format>
</match>

設定を変更したら td-agent を再起動し、データの送信を完了します。

image

以下の方法で送信されたデータを検証できます。 DQL

dql > L::nginx_td LIMIT 1
-----------------[ r1.nginx_td.s1 ]-----------------
    __docid 'L_c6et7vk5jjqulpr6osa0'
create_time 1637733374609
    date_ns 96184
       host 'df-solution-ecs-018'
    message '{"120.253.192.179 - - [24/Nov/2021":"13:55:10 +0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\" \"-\""}'
     source 'nginx_td'
       time 2021-11-24 13:56:06 +0800 CST
---------
1 rows, 1 series, cost 2ms

ログ解析(Pipeline)

一般的に、システムやサービスが生成するログは長い文字列です。各フィールドはスペースで区切られています。通常、ログは全体として取得されますが、ログ内の各フィールドの意味を分割して分析すると、表示されるデータがより明確になり、可視化にも便利です。
Pipeline は、Guance におけるテキストデータ処理の重要なコンポーネントです。その主な役割は、テキスト形式の文字列を具体的な構造化データに変換することで、Grok 正規表現と組み合わせて使用します。

Grok パターンの分類

DataKit の grok パターンは、グローバルパターンとローカルパターンの2つに分類できます。pattern ディレクトリ内のパターンファイルはすべてグローバルパターンであり、すべての Pipeline スクリプトで使用できます。一方、Pipeline スクリプト内で add_pattern() 関数を使用して追加されたパターンはローカルパターンであり、現在の Pipeline スクリプト内でのみ有効です。
DataKit の組み込みパターンですべてのユーザーニーズを満たせない場合、ユーザーは自分で Pipeline ディレクトリにパターンファイルを追加して拡張できます。カスタムパターンがグローバルレベルの場合は、pattern ディレクトリに新しいファイルを作成し、そのファイルにパターンを追加する必要があります。既存の組み込みパターンファイルに追加または変更しないでください。DataKit の起動時に組み込みパターンファイルが上書きされるためです。

ローカルパターンの追加

grok は、本質的にはテキストマッチングと抽出のために事前定義された正規表現であり、定義された正規表現に名前を付けて、ネストされた参照と拡張により無数の新しいパターンを簡単に作成できるようにしたものです。例えば、DataKit には以下の3つの組み込みパターンがあります。

_second (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)    #匹配秒数,_second为模式名
_minute (?:[0-5][0-9])                            #匹配分钟数,_minute为模式名
_hour (?:2[0123]|[01]?[0-9])                      #匹配年份,_hour为模式名

上記の3つの組み込みパターンに基づいて、独自の組み込みパターンを拡張し、time という名前を付けることができます。

# 把 time 加到 pattern 目录下文件中,此模式为全局模式,任何地方都能引用 time
time ([^0-9]?)%{hour:hour}:%{minute:minute}(?::%{second:second})([^0-9]?)

# 也可以通过 add_pattern() 添加到 pipeline 文件中,则此模式变为局部模式,只有当前 pipeline 脚本能使用 time
add_pattern(time, "([^0-9]?)%{HOUR:hour}:%{MINUTE:minute}(?::%{SECOND:second})([^0-9]?)")

# 通过 grok 提取原始输入中的时间字段。假定输入为 12:30:59,则提取到 {"hour": 12, "minute": 30, "second": 59}
grok(_, %{time})

注意: - 同じパターン名の場合、スクリプトレベルのものが優先されます(つまり、ローカルパターンがグローバルパターンを上書きします)。 - pipeline スクリプトでは、add_pattern()grok() 関数よりも前に呼び出す必要があります。そうしないと、最初のデータの抽出に失敗する可能性があります。

Nginx ログ解析の設定

Pipeline ファイルの作成

<datakit安装目录>/pipeline ディレクトリに Pipeline ファイルを作成します。ファイル名は nginx.p です。

add_pattern("date2", "%{YEAR}[./]%{MONTHNUM}[./]%{MONTHDAY} %{TIME}")

grok(_, "%{IPORHOST:client_ip} %{NOTSPACE:http_ident} %{NOTSPACE:http_auth} \\[%{HTTPDATE:time}\\] \"%{DATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\" %{INT:status_code} %{INT:bytes}")

# access log
add_pattern("access_common", "%{IPORHOST:client_ip} %{NOTSPACE:http_ident} %{NOTSPACE:http_auth} \\[%{HTTPDATE:time}\\] \"%{DATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\" %{INT:status_code} %{INT:bytes}")
grok(_, '%{access_common} "%{NOTSPACE:referrer}" "%{GREEDYDATA:agent}')
user_agent(agent)

# error log
grok(_, "%{date2:time} \\[%{LOGLEVEL:status}\\] %{GREEDYDATA:msg}, client: %{IPORHOST:client_ip}, server: %{IPORHOST:server}, request: \"%{DATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\", (upstream: \"%{GREEDYDATA:upstream}\", )?host: \"%{IPORHOST:ip_or_host}\"")
grok(_, "%{date2:time} \\[%{LOGLEVEL:status}\\] %{GREEDYDATA:msg}, client: %{IPORHOST:client_ip}, server: %{IPORHOST:server}, request: \"%{GREEDYDATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\", host: \"%{IPORHOST:ip_or_host}\"")
grok(_,"%{date2:time} \\[%{LOGLEVEL:status}\\] %{GREEDYDATA:msg}")

group_in(status, ["warn", "notice"], "warning")
group_in(status, ["error", "crit", "alert", "emerg"], "error")

cast(status_code, "int")
cast(bytes, "int")

group_between(status_code, [200,299], "OK", status)
group_between(status_code, [300,399], "notice", status)
group_between(status_code, [400,499], "warning", status)
group_between(status_code, [500,599], "error", status)


nullif(http_ident, "-")
nullif(http_auth, "-")
nullif(upstream, "")
default_time(time)

注意:分割の際には、タグキーとの名前重複の可能性を避ける必要があります(Pipeline フィールド命名に関する注意事項)

Pipeline ファイルのデバッグ

Grok パターンは数が多いため、手動でのマッチングは面倒です。DataKit はインタラクティブなコマンドラインツール grokq(Grok Query)を提供しています。

datakit --grokq
grokq > Mon Jan 25 19:41:17 CST 2021   # 此处输入你希望匹配的文本
        2 %{DATESTAMP_OTHER: ?}        # 工具会给出对应对的建议,越靠前匹配月精确(权重也越大)。前面的数字表明权重。
        0 %{GREEDYDATA: ?}

grokq > 2021-01-25T18:37:22.016+0800
        4 %{TIMESTAMP_ISO8601: ?}      # 此处的 ? 表示你需要用一个字段来命名匹配到的文本
        0 %{NOTSPACE: ?}
        0 %{PROG: ?}
        0 %{SYSLOGPROG: ?}
        0 %{GREEDYDATA: ?}             # 像 GREEDYDATA 这种范围很广的 pattern,权重都较低
                                       # 权重越高,匹配的精确度越大

grokq > Q                              # Q 或 exit 退出
Bye!

DataKit が提供するコマンドラインツール grokq を使用して Pipeline ファイルを作成したら、Pipeline スクリプト名(--pl、Pipeline スクリプトは /pipeline ディレクトリに配置する必要があります)を指定し、テキスト(--txt)を入力することで、抽出が成功したかどうかを判断できます。

#提取成功示例
datakit --pl nginx.p --txt '172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] "GET /datadoghq/company?test=var1%20Pl HTTP/1.1" 401 612 "http://www.perdu.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "-"'
Extracted data(cost: 5.279203ms):  # 表示切割成功
{
  "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\" \"-\"",
  "browser": "Chrome",
  "browserVer": "55.0.2883.87",
  "bytes": 612,
  "client_ip": "172.17.0.1",
  "engine": "AppleWebKit",
  "engineVer": "537.36",
  "http_method": "GET",
  "http_url": "/datadoghq/company?test=var1%20Pl",
  "http_version": "1.1",
  "isBot": false,
  "isMobile": false,
  "message": "172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] \"GET /datadoghq/company?test=var1%20Pl HTTP/1.1\" 401 612 \"http://www.perdu.com/\" \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\" \"-\"",
  "os": "Linux x86_64",
  "referrer": "http://www.perdu.com/",
  "status": "warning",
  "status_code": 401,
  "time": 1483719397000000000,
  "ua": "X11"
}

# 提取失败示例
datakit --pl nginx.p --txt '172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] "GET /datadoghq/company?test=var1%20Pl HTTP/1.1" 401 612 "http://www.perdu.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "-"'
No data extracted from pipeline

収集に Pipeline スクリプトを適用する設定

テキストログ収集の Pipeline スクリプト設定

Nginx ログ収集を例に、Logging 収集で pipeline フィールドを設定します。ここで設定するのは Pipeline スクリプトの名前であり、パスではないことに注意してください。ここで参照されるすべての Pipeline スクリプトは、 ディレクトリに配置する必要があります。

[inputs.logging]]
  # 日志文件列表,可以指定绝对路径,支持使用 glob 规则进行批量指定
  # 推荐使用绝对路径
  logfiles = [
    "/var/log/nginx/access.log",                         
    "/var/log/nginx/error.log",                      
  ]

  # 文件路径过滤,使用 glob 规则,符合任意一条过滤条件将不会对该文件进行采集
  ignore = [""]

  # 数据来源,如果为空,则默认使用 'default'
  source = ""

  # 新增标记tag,如果为空,则默认使用 $source
  service = ""

  # pipeline 脚本路径,如果为空将使用 $source.p,如果 $source.p 不存在将不使用 pipeline
  pipeline = "nginx.p"

  # 过滤对应 status:
  #   `emerg`,`alert`,`critical`,`error`,`warning`,`info`,`debug`,`OK`
  ignore_status = []

  # 选择编码,如果编码有误会导致数据无法查看。默认为空即可:
  #    `utf-8`, `utf-16le`, `utf-16le`, `gbk`, `gb18030` or ""
  character_encoding = ""

  ## 设置正则表达式,例如 ^\d{4}-\d{2}-\d{2} 行首匹配 YYYY-MM-DD 时间格式
  ## 符合此正则匹配的数据,将被认定为有效数据,否则会累积追加到上一条有效数据的末尾
  ## 使用3个单引号 '''this-regexp''' 避免转义
  ## 正则表达式链接:https://golang.org/pkg/regexp/syntax/#hdr-Syntax
  # multiline_match = '''^\S'''

  ## 是否删除 ANSI 转义码,例如标准输出的文本颜色等
  remove_ansi_escape_codes = false

  # 自定义 tags
  [inputs.logging.tags]
   app = oa

Datakit を再起動すると、対応するログが分割されます。

systemctl restart datakit
ストリーミングログ収集の Pipeline スクリプト設定

Fluentd ログ収集を例に、Match の Output を変更し、タイプを Http に指定し、Endpoint を Logstreaming が有効な DataKit アドレスに向け、Pipeline スクリプト名を設定することで収集が完了します。

##pc端日志收集
<source>
  @type tail
  format ltsv
  path /var/log/nginx/access.log
  pos_file /var/log/buffer/posfile/access.log.pos
  tag nginx
  time_key time
  time_format %d/%b/%Y:%H:%M:%S %z
</source>

##收集的数据由http协议转发至本地 DataKit
## nginx output
<match nginx>
  @type http
  endpoint http://127.0.0.1:9529/v1/write/logstreaming?source=nginx_td&pipeline=nginx.p
  open_timeout 2
  <format>
    @type json
  </format>
</match>
設定を変更したら td-agent を再起動し、データの送信を完了します。

ログ収集のパフォーマンス最適化

Pipeline の実行速度が遅い理由

パフォーマンスの問題はよく議論されます。ユーザーは、Grok 式を使用すると、Pipeline によるログ処理速度が非常に遅くなることに気づくことがよくあります。Grok パターンは正規表現に基づいています。Pipeline を作成する際に使用する Grok 変数がカバーするシナリオが多すぎるためであったり、行ごとに全量マッチングを行っているために処理速度が遅くなっている可能性があります。

2回マッチングされる式に注意

同じゲートウェイからの複数のアプリケーションログを処理する際に、多くの Grok パターンで問題が発生するのをよく見かけます。例えば、Syslog です。以下のようなシナリオを想像してみてください。"common_header: payload" というログ形式を使用して、3つのアプリケーションログを記録したとします。

Application 1: '8.8.8.8 process-name[666]: a b 1 2 a lot of text at the end'
Application 2: '8.8.8.8 process-name[667]: a 1 2 3 a lot of text near the end;4'
Application 3: '8.8.8.8 process-name[421]: a completely different format | 1111'
通常、1つの Pipeline ですべての3つのログを処理します。
grok(_ , "%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{WORD:word_1} %{WORD:word_2} %{NUMBER:number_1} %{NUMBER:number_2} %{DATA:data}")
grok(_ , "%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{WORD:word_1} %{NUMBER:number_1} %{NUMBER:number_2} %{NUMBER:number_3} %{DATA:data};%{NUMBER:number_4}")
grok(_ , "%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{DATA:data} | %{NUMBER:number}")
しかし、注意すべき点は、ログが正常にマッチングできる場合でも、Grok は順番に送られてきたログをマッチングしようとし、最初にマッチングに成功したログでこのループを Break します。そのため、どのような順序が最適かを自分で判断する必要があります。そうしないと、1つずつ順番に試行することになります。なぜなら、複数の異なる形式があるからです。一般的な最適化方法の1つは、階層マッチングを使用してこの Pipeline を最適化することです。
add_pattern("message", "%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{GREEDYDATA:message}")

grok(_, "%{message} %{WORD:word_1} %{WORD:word_2} %{NUMBER:number_1} %{NUMBER:number_2} %{GREEDYDATA:data}")
grok(_, "%{message} %{WORD:word_1} %{NUMBER:number_1} %{NUMBER:number_2} %{NUMBER:number_3} %{DATA:data};%{NUMBER:number_4}")
grok(_, "%{message} %{DATA:data} | %{NUMBER:number}")

パフォーマンスオーバーヘッドの大きい Grok 式に注意

以下のような Nginx ログを見てみましょう。

172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] "GET /datadoghq/company?test=var1%20Pl HTTP/1.1" 401 612 "http://www.perdu.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "-"
通常、1つの Pipeline で柔軟な Grok 式を使用して処理します。
grok(_, "%{IPORHOST:client_ip} %{NOTSPACE:http_ident} %{NOTSPACE:http_auth} \\[%{HTTPDATE:time}\\] \"%{DATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\" %{INT:status_code} %{INT:bytes}")

cast(status_code, "int")
cast(bytes, "int")

ここで、%{IPORHOST:client_ip} --> 172.17.0.1 のパフォーマンスオーバーヘッドは非常に大きくなります。なぜなら、Grok は内部的に正規表現に変換されるため、カバーするシナリオが多い Grok 式ほどパフォーマンスが低下する可能性があるからです。%{IPORHOST:client_ip} の基礎となる複雑な正規表現を見てみましょう。

IPORHOST (?:%{IP}|%{HOSTNAME})
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
IP (?:%{IPV6}|%{IPV4})
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?<![0-9])(?:(?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5]))(?![0-9])

たった1行の Grok 式にこれほど多くの複雑な正規表現が含まれていることがわかります。このことから、大量のログを処理する必要がある場合に、このような複雑な Grok 式を使用することがパフォーマンスにどれほど影響を与えるかがわかります。では、どのように最適化すればよいのでしょうか。

grok(_, "%{NOTSPACE:client_ip} %{NOTSPACE:http_ident} %{NOTSPACE:http_auth} \\[%{HTTPDATE:time}\\] \"%{DATA:http_method} %{GREEDYDATA:http_url} HTTP/%{NUMBER:http_version}\" %{INT:status_code} %{INT:bytes}")

cast(status_code, "int")
cast(bytes, "int")

default_time(time)

パフォーマンスを重視する場合は、可能な限り %{NOTSPACE:} を使用してください。grok は内部的に正規表現に変換されるため、カバーするシナリオが多い Grok 式ほどパフォーマンスが低下する可能性があります。逆に、%{NOTSPACE:}(非スペース)のような非常に単純なマッチングの変数は、パフォーマンスが高くなります。そのため、データが非スペースであることが確実で、データの直後に空白文字が続く場合は、%{NOTSPACE:} を選択して Pipeline のパフォーマンスを向上させましょう。

ツールを活用した Pipeline の作成

DataKit - インタラクティブコマンドラインツール grokq

Grok パターンは数が多いため、手動でのマッチングは面倒です。DataKit はインタラクティブなコマンドラインツール grokq(Grok Query)を提供しています。

datakit --grokq
grokq > Mon Jan 25 19:41:17 CST 2021   # 此处输入你希望匹配的文本
        2 %{DATESTAMP_OTHER: ?}        # 工具会给出对应对的建议,越靠前匹配月精确(权重也越大)。前面的数字表明权重。
        0 %{GREEDYDATA: ?}

grokq > 2021-01-25T18:37:22.016+0800
        4 %{TIMESTAMP_ISO8601: ?}      # 此处的 ? 表示你需要用一个字段来命名匹配到的文本
        0 %{NOTSPACE: ?}
        0 %{PROG: ?}
        0 %{SYSLOGPROG: ?}
        0 %{GREEDYDATA: ?}             # 像 GREEDYDATA 这种范围很广的 pattern,权重都较低
                                       # 权重越高,匹配的精确度越大

grokq > Q                              # Q 或 exit 退出
Bye!

DataKit - Pipeline スクリプトテスト

DataKit が提供するコマンドラインツール grokq を使用して Pipeline ファイルを作成したら、Pipeline スクリプト名(--pl、Pipeline スクリプトは /pipeline ディレクトリに配置する必要があります)を指定し、テキスト(--txt)を入力することで、抽出が成功したかどうかを判断できます。

#提取成功示例
datakit --pl nginx.p --txt '172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] "GET /datadoghq/company?test=var1%20Pl HTTP/1.1" 401 612 "http://www.perdu.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "-"'
Extracted data(cost: 5.279203ms):  # 表示切割成功
{
  "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\" \"-\"",
  "browser": "Chrome",
  "browserVer": "55.0.2883.87",
  "bytes": 612,
  "client_ip": "172.17.0.1",
  "engine": "AppleWebKit",
  "engineVer": "537.36",
  "http_method": "GET",
  "http_url": "/datadoghq/company?test=var1%20Pl",
  "http_version": "1.1",
  "isBot": false,
  "isMobile": false,
  "message": "172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] \"GET /datadoghq/company?test=var1%20Pl HTTP/1.1\" 401 612 \"http://www.perdu.com/\" \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\" \"-\"",
  "os": "Linux x86_64",
  "referrer": "http://www.perdu.com/",
  "status": "warning",
  "status_code": 401,
  "time": 1483719397000000000,
  "ua": "X11"
}

# 提取失败示例
datakit --pl nginx.p --txt '172.17.0.1 - - [06/Jan/2017:16:16:37 +0000] "GET /datadoghq/company?test=var1%20Pl HTTP/1.1" 401 612 "http://www.perdu.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "-"'
No data extracted from pipeline

オンライン Grok デバッグ

GrokDebug サイトを利用して Grok をデバッグします。

image

ログ収集のコスト最適化

Guance 製品側でのコスト最適化

「Guance」は、ログブラックリストを設定することで、条件に一致するログをフィルタリングすることをサポートしています。つまり、ログブラックリストを設定すると、条件に一致するログデータは「Guance」ワークスペースに送信されなくなり、ユーザーのログデータ保存コストを削減できます。

注意:ここでの設定は DataKit にプッシュ配布されるわけではありません。この設定は、DataKit がセンターの設定ファイルを能動的に Get リクエストし、そのファイルを読み取ってローカルでフィルタリングアクションを実行することで有効になります。

新しいログブラックリストの作成

「Guance」ワークスペースで、「ログ」-「ブラックリスト」-「ブラックリストを作成」をクリックし、「ログソース」を選択し、1つ以上のログフィルタリングルールを追加して、「確定」をクリックすると、デフォルトでこのログフィルタリングルールが有効になります。「ログブラックリスト」で、すべてのログフィルタリングルールを確認できます。
image
image
注意:ログフィルタリング条件は「and(かつ)」の関係です。つまり、フィルタリング条件をすべて満たすログデータは、ワークスペースに送信されません。

ストリーミングログ収集の事前コスト最適化

Fluentd ログ収集を例にすると、<match> </match> でログを集約して圧縮したり、<match> </match> の使用中にイベントをフィルタリングして、エラーや警告ログのみを Guance に送信することで、使用コストを削減できます。

詳細情報

テキストデータ処理(Pipeline)

Pipeline のデバッグ

ログ

サードパーティログの接続

フィードバック

このページは役に立ちましたか?