Grokking grok
Grokは再利用可能なエイリアス式をサポートする正規表現の方言です。Grokはsyslogログ、Apacheおよび他のウェブサーバーログ、mysqlログ、一般的に人間向けに書かれた任意のログ形式で非常にうまく機能します。
GrokはOniguruma正規表現ライブラリの上に位置しているため、grokではすべての正規表現が有効です。Grokはこの正規表現言語を使用して、既存のパターンに名前を付け、それらを組み合わせてフィールドに一致するより複雑なパターンを作成します。
Grok patterns
Elastic Stackには、grokを使用する際に作業を簡素化する多数の事前定義されたgrokパターンが付属しています。grokパターンを再利用するための構文は、次のいずれかの形式を取ります:
%{SYNTAX} |
%{SYNTAX:ID} |
%{SYNTAX:ID:TYPE} |
SYNTAX
- テキストに一致するパターンの名前。たとえば、
NUMBER
とIP
は、デフォルトのパターンセット内で提供される両方のパターンです。NUMBER
パターンは3.44
のようなデータに一致し、IP
パターンは55.3.244.1
のようなデータに一致します。 ID
- 一致するテキストの部分に付ける識別子。たとえば、
3.44
はイベントの期間である可能性があるため、duration
と呼ぶことができます。文字列55.3.244.1
は、リクエストを行っているclient
を識別するかもしれません。 TYPE
- 名前付きフィールドをキャストしたいデータ型。
int
、long
、double
、float
、およびboolean
がサポートされている型です。
たとえば、メッセージデータが次のように見えるとします:
Txt
3.44 55.3.244.1
最初の値は数字で、その後にIPアドレスのように見えるものが続きます。このテキストは、次のgrok式を使用して一致させることができます:
Txt
%{NUMBER:duration} %{IP:client}
Migrating to Elastic Common Schema (ECS)
Elastic Common Schema (ECS)への移行を容易にするために、既存のパターンに加えて新しいECS準拠のパターンセットが利用可能です。新しいECSパターン定義は、スキーマに準拠したイベントフィールド名をキャプチャします。
ECSパターンセットには、レガシーセットのすべてのパターン定義が含まれており、ドロップイン置換が可能です。ecs-compatability
設定を使用してモードを切り替えます。
新機能や改善点は、ECS準拠のファイルに追加されます。レガシーパターンは、後方互換性のあるバグ修正を受ける可能性があります。
Use grok patterns in Painless scripts
事前定義されたgrokパターンをPainlessスクリプトに組み込んでデータを抽出できます。スクリプトをテストするには、Painless execute APIのfield contextsを使用するか、スクリプトを含むランタイムフィールドを作成します。ランタイムフィールドはより柔軟性があり、複数のドキュメントを受け入れますが、Painless execute APIは、スクリプトをテストしているクラスターに書き込みアクセスがない場合に優れたオプションです。
データに一致するgrokパターンを構築するのに助けが必要な場合は、KibanaのGrok Debuggerツールを使用してください。
たとえば、Apacheログデータを扱っている場合、Apacheログの構造を理解する%{COMMONAPACHELOG}
構文を使用できます。サンプルドキュメントは次のようになります:
Js
"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - -
[30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
message
フィールドからIPアドレスを抽出するには、%{COMMONAPACHELOG}
構文を組み込んだPainlessスクリプトを書くことができます。このスクリプトは、Painless execute APIのip
フィールドコンテキストを使用してテストできますが、代わりにランタイムフィールドを使用しましょう。
サンプルドキュメントに基づいて、@timestamp
およびmessage
フィールドをインデックスします。柔軟性を保つために、wildcard
をmessage
のフィールドタイプとして使用します:
Python
resp = client.indices.create(
index="my-index",
mappings={
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index',
body: {
mappings: {
properties: {
"@timestamp": {
format: 'strict_date_optional_time||epoch_second',
type: 'date'
},
message: {
type: 'wildcard'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index",
mappings: {
properties: {
"@timestamp": {
format: "strict_date_optional_time||epoch_second",
type: "date",
},
message: {
type: "wildcard",
},
},
},
});
console.log(response);
Console
PUT /my-index/
{
"mappings": {
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
}
}
次に、bulk APIを使用して、my-index
にいくつかのログデータをインデックスします。
Python
resp = client.bulk(
index="my-index",
refresh=True,
operations=[
{
"index": {}
},
{
"timestamp": "2020-04-30T14:30:17-05:00",
"message": "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:30:53-05:00",
"message": "232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:31:12-05:00",
"message": "26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:31:19-05:00",
"message": "247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:31:22-05:00",
"message": "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:31:27-05:00",
"message": "252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
},
{
"index": {}
},
{
"timestamp": "2020-04-30T14:31:28-05:00",
"message": "not a valid apache log"
}
],
)
print(resp)
Ruby
response = client.bulk(
index: 'my-index',
refresh: true,
body: [
{
index: {}
},
{
timestamp: '2020-04-30T14:30:17-05:00',
message: '40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:30:53-05:00',
message: '232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:31:12-05:00',
message: '26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:31:19-05:00',
message: '247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] "GET /french/splash_inet.html HTTP/1.0" 200 3781'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:31:22-05:00',
message: '247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:31:27-05:00',
message: '252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
},
{
index: {}
},
{
timestamp: '2020-04-30T14:31:28-05:00',
message: 'not a valid apache log'
}
]
)
puts response
Js
const response = await client.bulk({
index: "my-index",
refresh: "true",
operations: [
{
index: {},
},
{
timestamp: "2020-04-30T14:30:17-05:00",
message:
'40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:30:53-05:00",
message:
'232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:31:12-05:00",
message:
'26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:31:19-05:00",
message:
'247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] "GET /french/splash_inet.html HTTP/1.0" 200 3781',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:31:22-05:00",
message:
'247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:31:27-05:00",
message:
'252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
},
{
index: {},
},
{
timestamp: "2020-04-30T14:31:28-05:00",
message: "not a valid apache log",
},
],
});
console.log(response);
Console
POST /my-index/_bulk?refresh
{"index":{}}
{"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:30:53-05:00","message":"232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:12-05:00","message":"26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:19-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:22-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:27-05:00","message":"252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:28-05:00","message":"not a valid apache log"}
Incorporate grok patterns and scripts in runtime fields
これで、Painlessスクリプトとgrokパターンを含むランタイムフィールドをマッピングに定義できます。パターンが一致する場合、スクリプトは一致するIPアドレスの値を出力します。パターンが一致しない場合(clientip != null
)、スクリプトはクラッシュせずにフィールド値を返します。
Python
resp = client.indices.put_mapping(
index="my-index",
runtime={
"http.clientip": {
"type": "ip",
"script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
}
},
)
print(resp)
Js
const response = await client.indices.putMapping({
index: "my-index",
runtime: {
"http.clientip": {
type: "ip",
script:
"\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ",
},
},
});
console.log(response);
Console
PUT my-index/_mappings
{
"runtime": {
"http.clientip": {
"type": "ip",
"script": """
String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
if (clientip != null) emit(clientip);
"""
}
}
}
また、同じランタイムフィールドを検索リクエストのコンテキストで定義できます。ランタイム定義とスクリプトは、以前にインデックスマッピングで定義されたものとまったく同じです。その定義をruntime_mappings
セクションの検索リクエストにコピーし、ランタイムフィールドに一致するクエリを含めます。このクエリは、インデックスマッピングのhttp.clientip
ランタイムフィールドに対して検索クエリを定義した場合と同じ結果を返しますが、この特定の検索のコンテキスト内でのみ返されます:
Python
resp = client.search(
index="my-index",
runtime_mappings={
"http.clientip": {
"type": "ip",
"script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
}
},
query={
"match": {
"http.clientip": "40.135.0.0"
}
},
fields=[
"http.clientip"
],
)
print(resp)
Js
const response = await client.search({
index: "my-index",
runtime_mappings: {
"http.clientip": {
type: "ip",
script:
"\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ",
},
},
query: {
match: {
"http.clientip": "40.135.0.0",
},
},
fields: ["http.clientip"],
});
console.log(response);
Console
GET my-index/_search
{
"runtime_mappings": {
"http.clientip": {
"type": "ip",
"script": """
String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
if (clientip != null) emit(clientip);
"""
}
},
"query": {
"match": {
"http.clientip": "40.135.0.0"
}
},
"fields" : ["http.clientip"]
}
Return calculated results
http.clientip
ランタイムフィールドを使用して、特定のIPアドレスを検索するためのシンプルなクエリを定義し、関連するすべてのフィールドを返すことができます。fields
パラメータは、_search
APIのすべてのフィールドに対して機能し、元の_source
の一部として送信されなかったフィールドでも機能します:
Python
resp = client.search(
index="my-index",
query={
"match": {
"http.clientip": "40.135.0.0"
}
},
fields=[
"http.clientip"
],
)
print(resp)
Ruby
response = client.search(
index: 'my-index',
body: {
query: {
match: {
'http.clientip' => '40.135.0.0'
}
},
fields: [
'http.clientip'
]
}
)
puts response
Js
const response = await client.search({
index: "my-index",
query: {
match: {
"http.clientip": "40.135.0.0",
},
},
fields: ["http.clientip"],
});
console.log(response);
Console
GET my-index/_search
{
"query": {
"match": {
"http.clientip": "40.135.0.0"
}
},
"fields" : ["http.clientip"]
}
レスポンスには、検索クエリで指定された特定のIPアドレスが含まれています。Painlessスクリプト内のgrokパターンは、ランタイム中にmessage
フィールドからこの値を抽出しました。
Console-Result
{
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_id" : "1iN2a3kBw4xTzEDqyYE0",
"_score" : 1.0,
"_source" : {
"timestamp" : "2020-04-30T14:30:17-05:00",
"message" : "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
},
"fields" : {
"http.clientip" : [
"40.135.0.0"
]
}
}
]
}
}