データの解析
Dissectは、定義されたパターンに対して単一のテキストフィールドを解析します。Dissectパターンは、破棄したい文字列の部分によって定義されます。文字列の各部分に特別な注意を払うことで、成功するDissectパターンを構築するのに役立ちます。
正規表現の力が必要ない場合は、grokの代わりにDissectパターンを使用してください。Dissectはgrokよりもはるかにシンプルな構文を使用し、全体的に通常は速くなります。Dissectの構文は透明です:Dissectに何をしたいかを伝えると、その結果を返します。
Dissectパターン
Dissectパターンは変数と区切り文字で構成されています。パーセント記号と波括弧%{}
で定義されたものは、%{clientip}
のように変数と見なされます。フィールド内のデータの任意の部分に変数を割り当て、その後、必要な部分だけを返すことができます。区切り文字は、変数の間にある任意の値で、スペース、ダッシュ、または他の区切り文字である可能性があります。
たとえば、message
フィールドを持つログデータが次のように見えるとしましょう:
Js
"message" : "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
データの各部分に変数を割り当てて、成功するDissectパターンを構築します。Dissectに正確に何を一致させたいかを伝えることを忘れないでください。
データの最初の部分はIPアドレスのように見えるので、%{clientip}
のような変数を割り当てることができます。次の2文字は、両側にスペースのあるダッシュです。各ダッシュに変数を割り当てるか、ダッシュとスペースを表す単一の変数を割り当てることができます。次は、タイムスタンプを含む一連のブラケットです。ブラケットは区切り文字なので、それらをDissectパターンに含めます。これまでのところ、データと一致するDissectパターンは次のようになります:
Js
247.37.0.0 - - [30/Apr/2020:14:31:22 -0500]
%{clientip} %{ident} %{auth} [%{@timestamp}]
message フィールドからのデータの最初のチャンク |
|
選択したデータチャンクに一致させるためのDissectパターン |
同じ論理を使用して、残りのデータチャンクのために変数を作成できます。二重引用符は区切り文字なので、それらをDissectパターンに含めます。パターンはGET
を%{verb}
変数に置き換えますが、HTTP
はパターンの一部として保持されます。
Js
\"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0
"%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}
2つのパターンを組み合わせると、次のようなDissectパターンが得られます:
Js
%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}
Dissectパターンができたので、どのようにテストして使用しますか?
PainlessでDissectパターンをテストする
DissectパターンをPainlessスクリプトに組み込んでデータを抽出できます。スクリプトをテストするには、Painless execute APIのフィールドコンテキストを使用するか、スクリプトを含むランタイムフィールドを作成します。ランタイムフィールドはより大きな柔軟性を提供し、複数のドキュメントを受け入れますが、スクリプトをテストしているクラスターに書き込みアクセスがない場合は、Painless execute APIが優れたオプションです。
たとえば、Painlessスクリプトとデータに一致する単一のドキュメントを含めて、Painless execute APIでDissectパターンをテストします。まず、message
フィールドをwildcard
データ型としてインデックスします:
Python
resp = client.indices.create(
index="my-index",
mappings={
"properties": {
"message": {
"type": "wildcard"
}
}
},
)
print(resp)
Ruby
response = client.indices.create(
index: 'my-index',
body: {
mappings: {
properties: {
message: {
type: 'wildcard'
}
}
}
}
)
puts response
Js
const response = await client.indices.create({
index: "my-index",
mappings: {
properties: {
message: {
type: "wildcard",
},
},
},
});
console.log(response);
Console
PUT my-index
{
"mappings": {
"properties": {
"message": {
"type": "wildcard"
}
}
}
}
HTTPレスポンスコードを取得したい場合は、response
値を抽出するPainlessスクリプトにDissectパターンを追加します。フィールドから値を抽出するには、この関数を使用します:
Painless
この例では、message
は<field_name>
で、response
は<field_value>
です:
Python
resp = client.scripts_painless_execute(
script={
"source": "\n String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{response} %{size}').extract(doc[\"message\"].value)?.response;\n if (response != null) emit(Integer.parseInt(response)); \n "
},
context="long_field",
context_setup={
"index": "my-index",
"document": {
"message": "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
}
},
)
print(resp)
Js
const response = await client.scriptsPainlessExecute({
script: {
source:
'\n String response=dissect(\'%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}\').extract(doc["message"].value)?.response;\n if (response != null) emit(Integer.parseInt(response)); \n ',
},
context: "long_field",
context_setup: {
index: "my-index",
document: {
message:
'247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0',
},
},
});
console.log(response);
Console
POST /_scripts/painless/_execute
{
"script": {
"source": """
String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
if (response != null) emit(Integer.parseInt(response));
"""
},
"context": "long_field",
"context_setup": {
"index": "my-index",
"document": {
"message": """247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0"""
}
}
}
ランタイムフィールドはemit メソッドを使用して値を返す必要があります。 |
|
レスポンスコードは整数なので、long_field コンテキストを使用します。 |
|
データに一致するサンプルドキュメントを含めます。 |
Console-Result
{
"result" : [
304
]
}
ランタイムフィールドでDissectパターンとスクリプトを使用する
機能するDissectパターンがある場合は、それをランタイムフィールドに追加してデータを操作できます。ランタイムフィールドはフィールドをインデックスする必要がないため、スクリプトとその機能を変更するための素晴らしい柔軟性があります。すでにPainless execute APIを使用してDissectパターンをテストしている場合は、その正確なPainlessスクリプトをランタイムフィールドで使用できます。
まず、message
フィールドをwildcard
型として前のセクションのように追加しますが、@timestamp
もdate
として追加して、他のユースケースのためにそのフィールドで操作したい場合に備えます:
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"
}
}
}
}
HTTPレスポンスコードをDissectパターンを使用して抽出したい場合は、http.response
のようなランタイムフィールドを作成できます:
Python
resp = client.indices.put_mapping(
index="my-index",
runtime={
"http.response": {
"type": "long",
"script": "\n String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{response} %{size}').extract(doc[\"message\"].value)?.response;\n if (response != null) emit(Integer.parseInt(response));\n "
}
},
)
print(resp)
Js
const response = await client.indices.putMapping({
index: "my-index",
runtime: {
"http.response": {
type: "long",
script:
'\n String response=dissect(\'%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}\').extract(doc["message"].value)?.response;\n if (response != null) emit(Integer.parseInt(response));\n ',
},
},
});
console.log(response);
Console
PUT my-index/_mappings
{
"runtime": {
"http.response": {
"type": "long",
"script": """
String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
if (response != null) emit(Integer.parseInt(response));
"""
}
}
}
取得したいフィールドをマッピングした後、ログデータからいくつかのレコードをElasticsearchにインデックスします。次のリクエストは、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=true
{"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"}
特定のHTTPレスポンスを検索するためのシンプルなクエリを定義して、関連するすべてのフィールドを返すことができます。検索APIのfields
パラメータを使用して、http.response
ランタイムフィールドを取得します。
Python
resp = client.search(
index="my-index",
query={
"match": {
"http.response": "304"
}
},
fields=[
"http.response"
],
)
print(resp)
Ruby
response = client.search(
index: 'my-index',
body: {
query: {
match: {
'http.response' => '304'
}
},
fields: [
'http.response'
]
}
)
puts response
Js
const response = await client.search({
index: "my-index",
query: {
match: {
"http.response": "304",
},
},
fields: ["http.response"],
});
console.log(response);
Console
GET my-index/_search
{
"query": {
"match": {
"http.response": "304"
}
},
"fields" : ["http.response"]
}
また、同じランタイムフィールドを検索リクエストのコンテキストで定義することもできます。ランタイム定義とスクリプトは、インデックスマッピングで以前に定義されたものとまったく同じです。その定義をruntime_mappings
セクションの検索リクエストにコピーし、ランタイムフィールドに一致するクエリを含めます。このクエリは、インデックスマッピングでhttp.response
ランタイムフィールドのために以前に定義された検索クエリと同じ結果を返しますが、この特定の検索のコンテキスト内でのみ返されます:
Python
resp = client.search(
index="my-index",
runtime_mappings={
"http.response": {
"type": "long",
"script": "\n String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{response} %{size}').extract(doc[\"message\"].value)?.response;\n if (response != null) emit(Integer.parseInt(response));\n "
}
},
query={
"match": {
"http.response": "304"
}
},
fields=[
"http.response"
],
)
print(resp)
Js
const response = await client.search({
index: "my-index",
runtime_mappings: {
"http.response": {
type: "long",
script:
'\n String response=dissect(\'%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}\').extract(doc["message"].value)?.response;\n if (response != null) emit(Integer.parseInt(response));\n ',
},
},
query: {
match: {
"http.response": "304",
},
},
fields: ["http.response"],
});
console.log(response);
Console
GET my-index/_search
{
"runtime_mappings": {
"http.response": {
"type": "long",
"script": """
String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
if (response != null) emit(Integer.parseInt(response));
"""
}
},
"query": {
"match": {
"http.response": "304"
}
},
"fields" : ["http.response"]
}
Console-Result
{
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_id" : "D47UqXkBByC8cgZrkbOm",
"_score" : 1.0,
"_source" : {
"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"
},
"fields" : {
"http.response" : [
304
]
}
}
]
}
}