Logstashのgrokフィルターの使い方|構文・カスタムパターン・パフォーマンス最適化
grokフィルターは、Logstashで非構造化のログ行を正規表現ベースのパターンで解析し、IPアドレスやタイムスタンプ、レスポンスコードといった意味のあるフィールドへ分解する仕組みです。ApacheログやSyslogのように「1行がテキストの塊」で届くデータを、ElasticsearchやOpenSearchで検索・集計できる構造化データに変換する起点になります。この記事では、%{SYNTAX:SEMANTIC}構文の読み方から、独自パターンの作り方、Grok Debuggerでの検証、GREEDYDATAを避ける高速化、_grokparsefailureの対処、DissectやRubyフィルターとの使い分けまで、Elastic公式の設定オプションと実際の設定ファイル例で解説します。
まとめ
- grokの基本構文は
%{パターン名:フィールド名}。%{NUMBER:bytes:int}のように型変換を付けられるが、対応はintとfloatのみ。 - 設定は
filter { grok { match => { "message" => "..." } } }。既定オプションはbreak_on_match=true・named_captures_only=true・tag_on_failure=["_grokparsefailure"]・timeout_millis=30000。 - 独自パターンはフィルタ内の
pattern_definitions(その場限り)か、patterns_dirで読み込む外部ファイル(複数フィルタで共有)で定義する。 - 高速化の要点は、行頭
^・行末$でアンカーする/GREEDYDATAや.*をNOTSPACEなど特定パターンへ置き換える/一般的なパターンを先頭に並べる。 - 構造が毎行そろっているログはDissectの方が高速。正規表現が必要な可変ログでgrokを使い、両者は併用もできる。
以下、構文・設定・カスタムパターン・検証・高速化・トラブル対処・フィルタの使い分けの順に、実際の設定例とともに見ていきます。
grokフィルターとは|Logstashでの役割と仕組み
Logstashは、入力(input)→加工(filter)→出力(output)の3段でログを処理するパイプラインです。grokはこのfilter段で使うプラグインで、届いた1行のログ(既定ではmessageフィールド)に正規表現パターンを当て、マッチした部分を名前付きフィールドとして切り出します。切り出したフィールドをElasticsearchやOpenSearchへ送ることで、レスポンスコード別の集計やIP別の絞り込みといった検索が初めて可能になります。ログ基盤としてFluentdを使う構成もあり、その全体像はFluentdを用いた監査ログシステムの基本アーキテクチャ設計と運用フローで扱っています。
grokの内部では、Rubyの正規表現エンジンOniguruma(Logstashを動かすJRuby上ではJava移植のJoni)が使われます。あらかじめ用意された名前付きパターン(IPV4やTIMESTAMP_ISO8601など)は、内部的には正規表現に展開されて評価されるため、実体は「読みやすい別名を付けた正規表現」だと理解しておくと、後述のパフォーマンス問題の原因も掴みやすくなります。生の正規表現を型安全に扱う発想はほかの言語にもあり、TypeScriptでの例はArkRegexとは何か|TypeScriptで型安全な正規表現を扱うライブラリが参考になります。
grok構文の基本|%{SYNTAX:SEMANTIC}の読み方と型変換
grokパターンの最小単位は%{SYNTAX:SEMANTIC}です。SYNTAX(構文)はマッチに使うパターン名、SEMANTIC(意味)は切り出した値に付けるフィールド名を指します。たとえば%{IPV4:client_ip}は「IPv4アドレスにマッチする部分をclient_ipという名前で取り出す」という意味になります。
型変換は:intと:floatのみ対応
切り出した値は既定では文字列になります。数値として扱いたい場合は3つ目に型を付けて%{NUMBER:response:int}や%{NUMBER:duration:float}と書きます。ここで指定できる型はElastic公式の仕様上intとfloatの2つだけです。日付やbooleanへの変換はgrokでは行えないため、dateフィルタやmutateフィルタなど別プラグインと組み合わせます。
よく使う組み込みパターン
Logstashには100以上の組み込みパターンが同梱されています。ログ解析で頻出するものを挙げます。用途に合った「なるべく狭いパターン」を選ぶことが、後述の高速化にも直結します。
| パターン名 | マッチ対象 | 典型的な用途 |
|---|---|---|
| IPV4 / IPORHOST | IPv4アドレス / ホスト名 | クライアントIP |
| NUMBER | 整数・小数 | レスポンスコード・バイト数 |
| WORD | 英数字の単語 | HTTPメソッド |
| NOTSPACE | 空白以外の連続文字 | URLパス・トークン |
| TIMESTAMP_ISO8601 | ISO8601日時 | ログの発生時刻 |
| LOGLEVEL | INFO/WARN/ERROR等 | ログレベル |
| GREEDYDATA | 任意の残り全部 | メッセージ本文(末尾のみ推奨) |
| COMBINEDAPACHELOG | Apache結合ログ1行 | アクセスログ一括抽出 |
COMBINEDAPACHELOGのように複数フィールドをまとめて抽出する複合パターンも用意されており、標準的なApacheログならこれ1つで主要フィールドがすべて揃います。
grokフィルターの基本設定|matchとconfファイルの書き方
grokはfilterブロック内にgrok { match => { ... } }の形で記述します。もっとも単純なのは、Apacheの結合ログを組み込みパターン1つで解析する例です。
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
}
個々のフィールドを自分で指定する場合は、ログ行の並びに合わせてパターンを連結します。次はアクセスログを手組みで分解し、レスポンスコードとバイト数を整数化する例です。
filter {
grok {
match => {
"message" => "%{IPORHOST:client_ip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{NOTSPACE:request} HTTP/%{NUMBER:http_version}\" %{NUMBER:response:int} %{NUMBER:bytes:int}"
}
}
}
複数パターンのマッチとbreak_on_match
1つのフィールドに対して複数の候補パターンを配列で渡せます。フォーマットが数種類に分岐するログで有効です。
filter {
grok {
match => {
"message" => [
"%{TIMESTAMP_ISO8601:ts} %{LOGLEVEL:level} %{GREEDYDATA:msg}",
"%{SYSLOGTIMESTAMP:ts} %{GREEDYDATA:msg}"
]
}
}
}
既定のbreak_on_matchはtrueで、最初にマッチしたパターンで打ち切ります。互いに排他的なフォーマットならこの既定のままが最速です。逆に、複数のパターンを重ねて情報を積み上げたいときだけfalseにします(その分すべてのパターンを評価するので遅くなります)。
主要オプションと既定値
grokフィルターの挙動を左右する主なオプションと、Elastic公式の既定値です。多くはそのままで問題ありませんが、overwriteやtimeout_millisは運用で調整する場面があります。
| オプション | 既定値 | 役割 |
|---|---|---|
| match | {} | 対象フィールドとパターンの対応 |
| break_on_match | true | 初回マッチで打ち切るか |
| named_captures_only | true | 名前付き取得のみ保持 |
| keep_empty_captures | false | 空マッチもフィールド化するか |
| overwrite | [] | 既存フィールドを上書きする対象 |
| patterns_dir | [] | 外部パターンファイルの場所 |
| pattern_definitions | {} | フィルタ内インライン定義 |
| tag_on_failure | [“_grokparsefailure”] | 失敗時に付けるタグ |
| timeout_millis | 30000 | 1マッチのタイムアウト(ms) |
overwriteは、messageを解析後に整形版で置き換えたいときなどに使います。指定しないと元フィールドは残り、二重にデータを持つことになります。
カスタムGrokパターンの作成|pattern_definitionsとpatterns_dir
組み込みパターンで表現できないログ形式は、独自パターンを定義します。定義の書式はどちらの方法でもパターン名 正規表現の1行です。使う頻度と共有範囲で使い分けます。
インライン定義(pattern_definitions)
そのフィルタ内だけで使う一時的なパターンはpattern_definitionsに直接書きます。設定ファイルを開けば定義が見える手軽さが利点です。次はPostfixのキューID(10〜11桁の16進)を独自定義する例です。
filter {
grok {
pattern_definitions => {
"QUEUEID" => "[0-9A-F]{10,11}"
}
match => { "message" => "%{SYSLOGTIMESTAMP:timestamp} %{QUEUEID:queue_id} %{GREEDYDATA:msg}" }
}
}
外部ファイル(patterns_dir)
複数のパイプラインやフィルタで同じパターンを使い回すなら、外部ファイルにまとめてpatterns_dirで読み込みます。ディレクトリ内の各ファイルに定義を並べます。
# /etc/logstash/patterns/postfix
QUEUEID [0-9A-F]{10,11}
filter {
grok {
patterns_dir => ["/etc/logstash/patterns"]
match => { "message" => "%{SYSLOGTIMESTAMP:timestamp} %{QUEUEID:queue_id}" }
}
}
大量の独自パターンを扱う本番環境では、外部ファイル方式にして定義を1か所に集約する方が、設定ファイルの重複を避けられて保守しやすくなります。
Grok Debuggerでパターンを検証する
grokパターンは一発で通ることが少なく、手元で試しながら詰めるのが現実的です。もっとも確実なのはKibanaに標準搭載されたGrok Debuggerで、Kibana → Dev Tools → Grok Debuggerから開けます。サンプルログとパターンを貼り付けると、抽出されるフィールドがJSONでその場に表示されます。Logstashと同じOnigurumaエンジンで評価されるため、本番パイプラインへ入れる前の検証に向いています。
Kibanaが手元にない場合は、ブラウザだけで使えるオンラインのgrokdebugger.comでも同様にパターンを試せます(ただしこちらは非公式の移植で%{NUMBER:status:int}のような型変換指定に非対応のため、型付きパターンの検証はKibana版で行います)。いずれのツールでも、まず%{GREEDYDATA:all}で全体を1フィールドに受けてから、先頭のフィールドを1つずつ確定させていくと、どのパターンでマッチが崩れるかを切り分けやすくなります。
grokフィルターのパフォーマンス最適化
grokの遅さは、ほぼすべてが正規表現のバックトラッキング(総当たり的な後戻り)に起因します。とくにマッチに失敗する行が多いと、Onigurumaがあらゆる位置で照合を試みて処理量が跳ね上がります。次の3点は効果が大きく、順に適用する価値があります。
行頭・行末をアンカーする
パターンを^(行頭)と$(行末)で挟むと、部分文字列としての探索範囲が狭まり、無駄な後戻りが減ります。ログ全体の形が決まっているなら、両端をアンカーするだけで体感できる差が出ます。
match => { "message" => "^%{TIMESTAMP_ISO8601:ts} %{LOGLEVEL:level} %{GREEDYDATA:msg}$" }
GREEDYDATAをNOTSPACEなど特定パターンに置き換える
GREEDYDATA(実体は.*)は「残り全部」を貪欲に飲み込むため、後続パターンとの兼ね合いで後戻りが多発します。URLパスやトークンのように空白を含まない値ならNOTSPACE、数値ならNUMBERのように、対象を狭く限定したパターンへ置き換えます。GREEDYDATAを使うのはメッセージ本文を丸ごと受ける末尾の1か所に限定するのが原則です。
マッチ順とタイムアウトを設計する
複数パターンを配列で渡すときは、実際に多いフォーマットを先頭に並べます。マッチは先頭から順に試されるため、頻度順に並べるだけで平均処理時間が下がります。加えて、暴走する正規表現への保険としてtimeout_millis(既定30000ms)が働き、超過した行にはtag_on_timeout(既定_groktimeout)が付きます。複雑なログを1本の巨大なgrokパターンで無理に処理するのは避けるべきです。行数が多くフォーマットが単純なログにgrokを使うと、後述のDissectに比べてCPU使用率が大幅に高くなることがあり、パターンの正しさより先に「そもそもgrokが適材か」を疑う方が効果的な場合があります。
_grokparsefailureのトラブルシューティングとフィルターの無効化
grokがパターンにマッチできなかった行には、既定で_grokparsefailureタグが付きます(tag_on_failureで変更可)。このタグはエラーではなく「このパターンでは解析できなかった」という印で、Elasticsearch側でtags:_grokparsefailureを検索すると、パターン漏れのログを洗い出せます。まずはそこで実際に落ちている行を確認し、パターンを追加・修正するのが基本の流れです。
解析できない行を保存したくない、あるいは特定条件でgrok処理を止めたい場合は、条件分岐で制御します。次は解析失敗行を破棄する例です。
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
tag_on_failure => ["_grokparsefailure"]
}
if "_grokparsefailure" in [tags] {
drop { }
}
}
grokフィルター自体を一時的に無効化したいときは、grok { ... }ブロックをコメントアウトするか、特定のログ種別だけifで囲ってgrokを通さないようにします。フィルタを消すのではなくif [type] == "raw" { }のように条件で迂回させると、他のログへの影響を出さずに切り分けられます。
Grok・Dissect・Rubyフィルターの使い分け
ログ解析のフィルタはgrokだけではありません。処理対象の性質によっては、grokより高速なDissectや、より柔軟なRubyフィルターが適します。Logstashのフィルタ選定でつまずくのはこの見極めなので、判断基準を整理します。
| フィルタ | 方式 | 速度 | 向くログ |
|---|---|---|---|
| grok | 正規表現 | 普通 | 構造が行ごとに変わる可変ログ |
| dissect | 区切り(デリミタ)ベース | 速い | 区切りが毎行一定のログ |
| ruby | 任意コード | 実装次第 | 条件付き加工など独自ロジック |
Dissectは正規表現を使わず、%{}で囲んだ部分をフィールド、それ以外を区切り文字(デリミタ)として切り出します。正規表現の後戻りが発生しないぶん軽く、公開されている比較ではgrokに対してCPU使用率が数割低く抑えられた例が報告されています。区切りが毎行そろっているログでは、grokよりDissectを第一候補にすべきです。
filter {
dissect {
mapping => { "message" => "%{ts} %{+ts} %{level} %{msg}" }
}
}
一方、「行の前半は一定だが後半のフォーマットが可変」というログは、前半をDissectで高速に切り、残りをgrokで処理するハイブリッドが有効です。フィールドの値を条件付きで加工したい、複数フィールドを計算して新しい値を作りたいといった正規表現では表現しきれない処理は、Rubyフィルターにコードを書いて対応します(logstash ruby filterで検索される領域)。切り出したフィールドの格納先には、Elasticsearch互換のマネージドサービスもあり、構築手順はAmazon OpenSearch Serviceとは|料金体系・構築手順・運用のポイントで解説しています。
よくある質問
grokフィルターとは何ですか?
Logstashのfilter段で使うプラグインで、非構造化のログ行に正規表現ベースのパターンを当て、IPアドレスやタイムスタンプなどの意味のあるフィールドへ分解します。ApacheログやSyslogを検索・集計できる構造化データに変換する起点になります。
grokの構文はどう書きますか?
基本は%{パターン名:フィールド名}です。たとえば%{IPV4:client_ip}でIPv4をclient_ipとして取り出します。数値化したいときは%{NUMBER:bytes:int}のように型を付けますが、対応する型はintとfloatのみです。
GREEDYDATAとNOTSPACEはどう使い分けますか?
GREEDYDATAは残り全部を貪欲に受けるため後戻りが増え、性能が落ちます。空白を含まない値(URLパスやトークン)はNOTSPACEのように狭いパターンへ置き換え、GREEDYDATAはメッセージ本文を受ける末尾の1か所に限定するのが原則です。
_grokparsefailureが出るのはなぜですか?
指定したパターンでログ行を解析できなかった行に、既定で付くタグです。エラーではなくパターン漏れの印なので、Elasticsearchでtags:_grokparsefailureを検索して落ちている行を確認し、パターンを追加・修正します。tag_on_failureでタグ名は変更できます。
GrokとDissectはどちらが速いですか?
区切りが毎行そろっているログではDissectの方が高速です。Dissectは正規表現を使わず区切り文字で切り出すため軽く、公開されている比較ではgrokよりCPU使用率が数割低く抑えられた例があります。構造が可変な行はgrok、両方混在するならハイブリッドが適します。