データの解析

Dissectは、定義されたパターンに対して単一のテキストフィールドを解析します。Dissectパターンは、破棄したい文字列の部分によって定義されます。文字列の各部分に特別な注意を払うことで、成功するDissectパターンを構築するのに役立ちます。

正規表現の力が必要ない場合は、grokの代わりにDissectパターンを使用してください。Dissectはgrokよりもはるかにシンプルな構文を使用し、全体的に通常は速くなります。Dissectの構文は透明です:Dissectに何をしたいかを伝えると、その結果を返します。

Dissectパターン

Dissectパターンは変数区切り文字で構成されています。パーセント記号と波括弧%{}で定義されたものは、%{clientip}のように変数と見なされます。フィールド内のデータの任意の部分に変数を割り当て、その後、必要な部分だけを返すことができます。区切り文字は、変数の間にある任意の値で、スペース、ダッシュ、または他の区切り文字である可能性があります。

たとえば、messageフィールドを持つログデータが次のように見えるとしましょう:

Js

  1. "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

  1. 247.37.0.0 - - [30/Apr/2020:14:31:22 -0500]
  2. %{clientip} %{ident} %{auth} [%{@timestamp}]
messageフィールドからのデータの最初のチャンク
選択したデータチャンクに一致させるためのDissectパターン

同じ論理を使用して、残りのデータチャンクのために変数を作成できます。二重引用符は区切り文字なので、それらをDissectパターンに含めます。パターンはGET%{verb}変数に置き換えますが、HTTPはパターンの一部として保持されます。

Js

  1. \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0
  2. "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}

2つのパターンを組み合わせると、次のようなDissectパターンが得られます:

Js

  1. %{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

  1. resp = client.indices.create(
  2. index="my-index",
  3. mappings={
  4. "properties": {
  5. "message": {
  6. "type": "wildcard"
  7. }
  8. }
  9. },
  10. )
  11. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index',
  3. body: {
  4. mappings: {
  5. properties: {
  6. message: {
  7. type: 'wildcard'
  8. }
  9. }
  10. }
  11. }
  12. )
  13. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index",
  3. mappings: {
  4. properties: {
  5. message: {
  6. type: "wildcard",
  7. },
  8. },
  9. },
  10. });
  11. console.log(response);

Console

  1. PUT my-index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "message": {
  6. "type": "wildcard"
  7. }
  8. }
  9. }
  10. }

HTTPレスポンスコードを取得したい場合は、response値を抽出するPainlessスクリプトにDissectパターンを追加します。フィールドから値を抽出するには、この関数を使用します:

Painless

この例では、message<field_name>で、response<field_value>です:

Python

  1. resp = client.scripts_painless_execute(
  2. script={
  3. "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 "
  4. },
  5. context="long_field",
  6. context_setup={
  7. "index": "my-index",
  8. "document": {
  9. "message": "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
  10. }
  11. },
  12. )
  13. print(resp)

Js

  1. const response = await client.scriptsPainlessExecute({
  2. script: {
  3. source:
  4. '\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 ',
  5. },
  6. context: "long_field",
  7. context_setup: {
  8. index: "my-index",
  9. document: {
  10. message:
  11. '247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0',
  12. },
  13. },
  14. });
  15. console.log(response);

Console

  1. POST /_scripts/painless/_execute
  2. {
  3. "script": {
  4. "source": """
  5. String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
  6. if (response != null) emit(Integer.parseInt(response));
  7. """
  8. },
  9. "context": "long_field",
  10. "context_setup": {
  11. "index": "my-index",
  12. "document": {
  13. "message": """247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0"""
  14. }
  15. }
  16. }
ランタイムフィールドはemitメソッドを使用して値を返す必要があります。
レスポンスコードは整数なので、long_fieldコンテキストを使用します。
データに一致するサンプルドキュメントを含めます。

結果にはHTTPレスポンスコードが含まれます:

Console-Result

  1. {
  2. "result" : [
  3. 304
  4. ]
  5. }

ランタイムフィールドでDissectパターンとスクリプトを使用する

機能するDissectパターンがある場合は、それをランタイムフィールドに追加してデータを操作できます。ランタイムフィールドはフィールドをインデックスする必要がないため、スクリプトとその機能を変更するための素晴らしい柔軟性があります。すでにPainless execute APIを使用してDissectパターンをテストしている場合は、その正確なPainlessスクリプトをランタイムフィールドで使用できます。

まず、messageフィールドをwildcard型として前のセクションのように追加しますが、@timestampdateとして追加して、他のユースケースのためにそのフィールドで操作したい場合に備えます:

Python

  1. resp = client.indices.create(
  2. index="my-index",
  3. mappings={
  4. "properties": {
  5. "@timestamp": {
  6. "format": "strict_date_optional_time||epoch_second",
  7. "type": "date"
  8. },
  9. "message": {
  10. "type": "wildcard"
  11. }
  12. }
  13. },
  14. )
  15. print(resp)

Ruby

  1. response = client.indices.create(
  2. index: 'my-index',
  3. body: {
  4. mappings: {
  5. properties: {
  6. "@timestamp": {
  7. format: 'strict_date_optional_time||epoch_second',
  8. type: 'date'
  9. },
  10. message: {
  11. type: 'wildcard'
  12. }
  13. }
  14. }
  15. }
  16. )
  17. puts response

Js

  1. const response = await client.indices.create({
  2. index: "my-index",
  3. mappings: {
  4. properties: {
  5. "@timestamp": {
  6. format: "strict_date_optional_time||epoch_second",
  7. type: "date",
  8. },
  9. message: {
  10. type: "wildcard",
  11. },
  12. },
  13. },
  14. });
  15. console.log(response);

Console

  1. PUT /my-index/
  2. {
  3. "mappings": {
  4. "properties": {
  5. "@timestamp": {
  6. "format": "strict_date_optional_time||epoch_second",
  7. "type": "date"
  8. },
  9. "message": {
  10. "type": "wildcard"
  11. }
  12. }
  13. }
  14. }

HTTPレスポンスコードをDissectパターンを使用して抽出したい場合は、http.responseのようなランタイムフィールドを作成できます:

Python

  1. resp = client.indices.put_mapping(
  2. index="my-index",
  3. runtime={
  4. "http.response": {
  5. "type": "long",
  6. "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 "
  7. }
  8. },
  9. )
  10. print(resp)

Js

  1. const response = await client.indices.putMapping({
  2. index: "my-index",
  3. runtime: {
  4. "http.response": {
  5. type: "long",
  6. script:
  7. '\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 ',
  8. },
  9. },
  10. });
  11. console.log(response);

Console

  1. PUT my-index/_mappings
  2. {
  3. "runtime": {
  4. "http.response": {
  5. "type": "long",
  6. "script": """
  7. String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
  8. if (response != null) emit(Integer.parseInt(response));
  9. """
  10. }
  11. }
  12. }

取得したいフィールドをマッピングした後、ログデータからいくつかのレコードをElasticsearchにインデックスします。次のリクエストは、bulk APIを使用して生のログデータをmy-indexにインデックスします:

Python

  1. resp = client.bulk(
  2. index="my-index",
  3. refresh=True,
  4. operations=[
  5. {
  6. "index": {}
  7. },
  8. {
  9. "timestamp": "2020-04-30T14:30:17-05:00",
  10. "message": "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
  11. },
  12. {
  13. "index": {}
  14. },
  15. {
  16. "timestamp": "2020-04-30T14:30:53-05:00",
  17. "message": "232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
  18. },
  19. {
  20. "index": {}
  21. },
  22. {
  23. "timestamp": "2020-04-30T14:31:12-05:00",
  24. "message": "26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
  25. },
  26. {
  27. "index": {}
  28. },
  29. {
  30. "timestamp": "2020-04-30T14:31:19-05:00",
  31. "message": "247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"
  32. },
  33. {
  34. "index": {}
  35. },
  36. {
  37. "timestamp": "2020-04-30T14:31:22-05:00",
  38. "message": "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
  39. },
  40. {
  41. "index": {}
  42. },
  43. {
  44. "timestamp": "2020-04-30T14:31:27-05:00",
  45. "message": "252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
  46. },
  47. {
  48. "index": {}
  49. },
  50. {
  51. "timestamp": "2020-04-30T14:31:28-05:00",
  52. "message": "not a valid apache log"
  53. }
  54. ],
  55. )
  56. print(resp)

Ruby

  1. response = client.bulk(
  2. index: 'my-index',
  3. refresh: true,
  4. body: [
  5. {
  6. index: {}
  7. },
  8. {
  9. timestamp: '2020-04-30T14:30:17-05:00',
  10. message: '40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
  11. },
  12. {
  13. index: {}
  14. },
  15. {
  16. timestamp: '2020-04-30T14:30:53-05:00',
  17. message: '232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
  18. },
  19. {
  20. index: {}
  21. },
  22. {
  23. timestamp: '2020-04-30T14:31:12-05:00',
  24. message: '26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
  25. },
  26. {
  27. index: {}
  28. },
  29. {
  30. timestamp: '2020-04-30T14:31:19-05:00',
  31. message: '247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] "GET /french/splash_inet.html HTTP/1.0" 200 3781'
  32. },
  33. {
  34. index: {}
  35. },
  36. {
  37. timestamp: '2020-04-30T14:31:22-05:00',
  38. message: '247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0'
  39. },
  40. {
  41. index: {}
  42. },
  43. {
  44. timestamp: '2020-04-30T14:31:27-05:00',
  45. message: '252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736'
  46. },
  47. {
  48. index: {}
  49. },
  50. {
  51. timestamp: '2020-04-30T14:31:28-05:00',
  52. message: 'not a valid apache log'
  53. }
  54. ]
  55. )
  56. puts response

Js

  1. const response = await client.bulk({
  2. index: "my-index",
  3. refresh: "true",
  4. operations: [
  5. {
  6. index: {},
  7. },
  8. {
  9. timestamp: "2020-04-30T14:30:17-05:00",
  10. message:
  11. '40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
  12. },
  13. {
  14. index: {},
  15. },
  16. {
  17. timestamp: "2020-04-30T14:30:53-05:00",
  18. message:
  19. '232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
  20. },
  21. {
  22. index: {},
  23. },
  24. {
  25. timestamp: "2020-04-30T14:31:12-05:00",
  26. message:
  27. '26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
  28. },
  29. {
  30. index: {},
  31. },
  32. {
  33. timestamp: "2020-04-30T14:31:19-05:00",
  34. message:
  35. '247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] "GET /french/splash_inet.html HTTP/1.0" 200 3781',
  36. },
  37. {
  38. index: {},
  39. },
  40. {
  41. timestamp: "2020-04-30T14:31:22-05:00",
  42. message:
  43. '247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0',
  44. },
  45. {
  46. index: {},
  47. },
  48. {
  49. timestamp: "2020-04-30T14:31:27-05:00",
  50. message:
  51. '252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736',
  52. },
  53. {
  54. index: {},
  55. },
  56. {
  57. timestamp: "2020-04-30T14:31:28-05:00",
  58. message: "not a valid apache log",
  59. },
  60. ],
  61. });
  62. console.log(response);

Console

  1. POST /my-index/_bulk?refresh=true
  2. {"index":{}}
  3. {"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"}
  4. {"index":{}}
  5. {"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"}
  6. {"index":{}}
  7. {"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"}
  8. {"index":{}}
  9. {"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"}
  10. {"index":{}}
  11. {"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"}
  12. {"index":{}}
  13. {"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"}
  14. {"index":{}}
  15. {"timestamp":"2020-04-30T14:31:28-05:00","message":"not a valid apache log"}

特定のHTTPレスポンスを検索するためのシンプルなクエリを定義して、関連するすべてのフィールドを返すことができます。検索APIのfieldsパラメータを使用して、http.responseランタイムフィールドを取得します。

Python

  1. resp = client.search(
  2. index="my-index",
  3. query={
  4. "match": {
  5. "http.response": "304"
  6. }
  7. },
  8. fields=[
  9. "http.response"
  10. ],
  11. )
  12. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index',
  3. body: {
  4. query: {
  5. match: {
  6. 'http.response' => '304'
  7. }
  8. },
  9. fields: [
  10. 'http.response'
  11. ]
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.search({
  2. index: "my-index",
  3. query: {
  4. match: {
  5. "http.response": "304",
  6. },
  7. },
  8. fields: ["http.response"],
  9. });
  10. console.log(response);

Console

  1. GET my-index/_search
  2. {
  3. "query": {
  4. "match": {
  5. "http.response": "304"
  6. }
  7. },
  8. "fields" : ["http.response"]
  9. }

また、同じランタイムフィールドを検索リクエストのコンテキストで定義することもできます。ランタイム定義とスクリプトは、インデックスマッピングで以前に定義されたものとまったく同じです。その定義をruntime_mappingsセクションの検索リクエストにコピーし、ランタイムフィールドに一致するクエリを含めます。このクエリは、インデックスマッピングでhttp.responseランタイムフィールドのために以前に定義された検索クエリと同じ結果を返しますが、この特定の検索のコンテキスト内でのみ返されます:

Python

  1. resp = client.search(
  2. index="my-index",
  3. runtime_mappings={
  4. "http.response": {
  5. "type": "long",
  6. "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 "
  7. }
  8. },
  9. query={
  10. "match": {
  11. "http.response": "304"
  12. }
  13. },
  14. fields=[
  15. "http.response"
  16. ],
  17. )
  18. print(resp)

Js

  1. const response = await client.search({
  2. index: "my-index",
  3. runtime_mappings: {
  4. "http.response": {
  5. type: "long",
  6. script:
  7. '\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 ',
  8. },
  9. },
  10. query: {
  11. match: {
  12. "http.response": "304",
  13. },
  14. },
  15. fields: ["http.response"],
  16. });
  17. console.log(response);

Console

  1. GET my-index/_search
  2. {
  3. "runtime_mappings": {
  4. "http.response": {
  5. "type": "long",
  6. "script": """
  7. String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;
  8. if (response != null) emit(Integer.parseInt(response));
  9. """
  10. }
  11. },
  12. "query": {
  13. "match": {
  14. "http.response": "304"
  15. }
  16. },
  17. "fields" : ["http.response"]
  18. }

Console-Result

  1. {
  2. "hits" : {
  3. "total" : {
  4. "value" : 1,
  5. "relation" : "eq"
  6. },
  7. "max_score" : 1.0,
  8. "hits" : [
  9. {
  10. "_index" : "my-index",
  11. "_id" : "D47UqXkBByC8cgZrkbOm",
  12. "_score" : 1.0,
  13. "_source" : {
  14. "timestamp" : "2020-04-30T14:31:22-05:00",
  15. "message" : "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
  16. },
  17. "fields" : {
  18. "http.response" : [
  19. 304
  20. ]
  21. }
  22. }
  23. ]
  24. }
  25. }