ランタイムフィールドでデータを探索する
抽出したいフィールドがある大規模なログデータセットを考えてみてください。データのインデックス作成は時間がかかり、多くのディスクスペースを使用しますが、事前にスキーマを決定せずにデータ構造を探索したいだけです。
ログデータには抽出したい特定のフィールドが含まれていることがわかっています。この場合、@timestamp
とmessage
フィールドに焦点を当てます。ランタイムフィールドを使用することで、検索時にこれらのフィールドの値を計算するスクリプトを定義できます。
インデックスフィールドを出発点として定義する
Ruby
response = client.indices.create(
index: 'my-index-000001',
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-000001",
mappings: {
properties: {
"@timestamp": {
format: "strict_date_optional_time||epoch_second",
type: "date",
},
message: {
type: "wildcard",
},
},
},
});
console.log(response);
Console
PUT /my-index-000001/
{
"mappings": {
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
}
}
データを取り込む
取得したいフィールドをマッピングした後、ログデータからいくつかのレコードをElasticsearchにインデックスします。次のリクエストは、bulk APIを使用して、生のログデータをmy-index-000001
にインデックスします。すべてのログデータをインデックスする代わりに、小さなサンプルを使用してランタイムフィールドを試すことができます。
最終的なドキュメントは有効なApacheログ形式ではありませんが、そのシナリオをスクリプトで考慮することができます。
Python
resp = client.bulk(
index="my-index-000001",
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-000001',
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-000001",
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-000001/_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"}
この時点で、Elasticsearchが生データをどのように保存しているかを確認できます。
Python
resp = client.indices.get(
index="my-index-000001",
)
print(resp)
Ruby
response = client.indices.get(
index: 'my-index-000001'
)
puts response
Js
const response = await client.indices.get({
index: "my-index-000001",
});
console.log(response);
Console
GET /my-index-000001
マッピングには、@timestamp
とmessage
の2つのフィールドが含まれています。
Console-Result
{
"my-index-000001" : {
"aliases" : { },
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_second"
},
"message" : {
"type" : "wildcard"
},
"timestamp" : {
"type" : "date"
}
}
},
...
}
}
grokパターンでランタイムフィールドを定義する
スクリプトは、Apacheログの構造を理解する`````%{COMMONAPACHELOG}`````ログパターンにマッチします。パターンが一致すると(`````clientip != null`````)、スクリプトは一致したIPアドレスの値を出力します。パターンが一致しない場合、スクリプトはクラッシュせずにフィールド値を返します。
#### Python
``````python
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.client_ip": {
"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-000001",
runtime: {
"http.client_ip": {
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-000001/_mappings
{
"runtime": {
"http.client_ip": {
"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-000001",
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-000001",
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-000001/_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"]
}
複合ランタイムフィールドを定義する
単一のスクリプトから複数のフィールドを出力する複合ランタイムフィールドを定義することもできます。型付きのサブフィールドのセットを定義し、値のマップを出力できます。検索時に、各サブフィールドはマップ内の名前に関連付けられた値を取得します。これにより、grokパターンを1回だけ指定し、複数の値を返すことができます:
Python
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http": {
"type": "composite",
"script": "emit(grok(\"%{COMMONAPACHELOG}\").extract(doc[\"message\"].value))",
"fields": {
"clientip": {
"type": "ip"
},
"verb": {
"type": "keyword"
},
"response": {
"type": "long"
}
}
}
},
)
print(resp)
Ruby
response = client.indices.put_mapping(
index: 'my-index-000001',
body: {
runtime: {
http: {
type: 'composite',
script: 'emit(grok("%<COMMONAPACHELOG>s").extract(doc["message"].value))',
fields: {
clientip: {
type: 'ip'
},
verb: {
type: 'keyword'
},
response: {
type: 'long'
}
}
}
}
}
)
puts response
Js
const response = await client.indices.putMapping({
index: "my-index-000001",
runtime: {
http: {
type: "composite",
script: 'emit(grok("%{COMMONAPACHELOG}").extract(doc["message"].value))',
fields: {
clientip: {
type: "ip",
},
verb: {
type: "keyword",
},
response: {
type: "long",
},
},
},
},
});
console.log(response);
Console
PUT my-index-000001/_mappings
{
"runtime": {
"http": {
"type": "composite",
"script": "emit(grok(\"%{COMMONAPACHELOG}\").extract(doc[\"message\"].value))",
"fields": {
"clientip": {
"type": "ip"
},
"verb": {
"type": "keyword"
},
"response": {
"type": "long"
}
}
}
}
}
特定のIPアドレスを検索する
#### Python
``````python
resp = client.search(
index="my-index-000001",
query={
"match": {
"http.clientip": "40.135.0.0"
}
},
fields=[
"*"
],
)
print(resp)
`
Js
const response = await client.search({
index: "my-index-000001",
query: {
match: {
"http.clientip": "40.135.0.0",
},
},
fields: ["*"],
});
console.log(response);
Console
GET my-index-000001/_search
{
"query": {
"match": {
"http.clientip": "40.135.0.0"
}
},
"fields" : ["*"]
}
APIは次の結果を返します。http
はcomposite
ランタイムフィールドであるため、レスポンスにはfields
の下にある各サブフィールドが含まれ、クエリに一致する関連する値が含まれます。事前にデータ構造を構築することなく、データを意味のある方法で検索および探索し、インデックスするフィールドを決定することができます。
Console-Result
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_id" : "sRVHBnwBB-qjgFni7h_O",
"_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.verb" : [
"GET"
],
"http.clientip" : [
"40.135.0.0"
],
"http.response" : [
200
],
"message" : [
"40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
],
"http.client_ip" : [
"40.135.0.0"
],
"timestamp" : [
"2020-04-30T19:30:17.000Z"
]
}
}
]
}
}
また、スクリプト内のif
ステートメントを覚えていますか?
Painless
if (clientip != null) emit(clientip);
スクリプトにこの条件が含まれていなかった場合、クエリはパターンに一致しないシャードで失敗します。この条件を含めることで、クエリはgrokパターンに一致しないデータをスキップします。
特定の範囲内のドキュメントを検索する
#### Python
``````python
resp = client.search(
index="my-index-000001",
query={
"range": {
"timestamp": {
"gte": "2020-04-30T14:31:27-05:00"
}
}
},
)
print(resp)
`
Js
const response = await client.search({
index: "my-index-000001",
query: {
range: {
timestamp: {
gte: "2020-04-30T14:31:27-05:00",
},
},
},
});
console.log(response);
Console
GET my-index-000001/_search
{
"query": {
"range": {
"timestamp": {
"gte": "2020-04-30T14:31:27-05:00"
}
}
}
}
レスポンスには、ログ形式が一致しないドキュメントが含まれていますが、タイムスタンプは定義された範囲内にあります。
Console-Result
{
...
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_id" : "hdEhyncBRSB6iD-PoBqe",
"_score" : 1.0,
"_source" : {
"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" : "my-index-000001",
"_id" : "htEhyncBRSB6iD-PoBqe",
"_score" : 1.0,
"_source" : {
"timestamp" : "2020-04-30T14:31:28-05:00",
"message" : "not a valid apache log"
}
}
]
}
}
dissectパターンでランタイムフィールドを定義する
正規表現の力が必要ない場合は、grokパターンの代わりにdissectパターンを使用できます。dissectパターンは固定の区切り文字にマッチしますが、通常はgrokよりも高速です。
dissectを使用して、Apacheログをgrokパターンで解析するのと同じ結果を得ることができます。ログパターンにマッチするのではなく、破棄したい文字列の部分を含めます。破棄したい文字列の部分に特に注意を払うことで、成功するdissectパターンを構築するのに役立ちます。
Python
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.client.ip": {
"type": "ip",
"script": "\n String clientip=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
}
},
)
print(resp)
Js
const response = await client.indices.putMapping({
index: "my-index-000001",
runtime: {
"http.client.ip": {
type: "ip",
script:
'\n String clientip=dissect(\'%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{status} %{size}\').extract(doc["message"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ',
},
},
});
console.log(response);
Console
PUT my-index-000001/_mappings
{
"runtime": {
"http.client.ip": {
"type": "ip",
"script": """
String clientip=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{status} %{size}').extract(doc["message"].value)?.clientip;
if (clientip != null) emit(clientip);
"""
}
}
}
同様に、HTTPレスポンスコードを抽出するためのdissectパターンを定義できます:
Python
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.responses": {
"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-000001",
runtime: {
"http.responses": {
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-000001/_mappings
{
"runtime": {
"http.responses": {
"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));
"""
}
}
}
次に、http.responses
ランタイムフィールドを使用して特定のHTTPレスポンスを取得するクエリを実行できます。_search
リクエストのfields
パラメータを使用して、取得したいフィールドを指定します:
Python
resp = client.search(
index="my-index-000001",
query={
"match": {
"http.responses": "304"
}
},
fields=[
"http.client_ip",
"timestamp",
"http.verb"
],
)
print(resp)
Ruby
response = client.search(
index: 'my-index-000001',
body: {
query: {
match: {
'http.responses' => '304'
}
},
fields: [
'http.client_ip',
'timestamp',
'http.verb'
]
}
)
puts response
Js
const response = await client.search({
index: "my-index-000001",
query: {
match: {
"http.responses": "304",
},
},
fields: ["http.client_ip", "timestamp", "http.verb"],
});
console.log(response);
Console
GET my-index-000001/_search
{
"query": {
"match": {
"http.responses": "304"
}
},
"fields" : ["http.client_ip","timestamp","http.verb"]
}
レスポンスには、HTTPレスポンスが304
である単一のドキュメントが含まれています。
Console-Result
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_id" : "A2qDy3cBWRMvVAuI7F8M",
"_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.verb" : [
"GET"
],
"http.client_ip" : [
"247.37.0.0"
],
"timestamp" : [
"2020-04-30T19:31:22.000Z"
]
}
}
]
}
}