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
  • テキストに一致するパターンの名前。たとえば、NUMBERIPは、デフォルトのパターンセット内で提供される両方のパターンです。NUMBERパターンは3.44のようなデータに一致し、IPパターンは55.3.244.1のようなデータに一致します。
  • ID
  • 一致するテキストの部分に付ける識別子。たとえば、3.44はイベントの期間である可能性があるため、durationと呼ぶことができます。文字列55.3.244.1は、リクエストを行っているclientを識別するかもしれません。
  • TYPE
  • 名前付きフィールドをキャストしたいデータ型。intlongdoublefloat、およびbooleanがサポートされている型です。

たとえば、メッセージデータが次のように見えるとします:

Txt

  1. 3.44 55.3.244.1

最初の値は数字で、その後にIPアドレスのように見えるものが続きます。このテキストは、次のgrok式を使用して一致させることができます:

Txt

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

  1. "timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - -
  2. [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フィールドをインデックスします。柔軟性を保つために、wildcardmessageのフィールドタイプとして使用します:

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. }

次に、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
  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"}

Incorporate grok patterns and scripts in runtime fields

これで、Painlessスクリプトとgrokパターンを含むランタイムフィールドをマッピングに定義できます。パターンが一致する場合、スクリプトは一致するIPアドレスの値を出力します。パターンが一致しない場合(clientip != null)、スクリプトはクラッシュせずにフィールド値を返します。

Python

  1. resp = client.indices.put_mapping(
  2. index="my-index",
  3. runtime={
  4. "http.clientip": {
  5. "type": "ip",
  6. "script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
  7. }
  8. },
  9. )
  10. print(resp)

Js

  1. const response = await client.indices.putMapping({
  2. index: "my-index",
  3. runtime: {
  4. "http.clientip": {
  5. type: "ip",
  6. script:
  7. "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ",
  8. },
  9. },
  10. });
  11. console.log(response);

Console

  1. PUT my-index/_mappings
  2. {
  3. "runtime": {
  4. "http.clientip": {
  5. "type": "ip",
  6. "script": """
  7. String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
  8. if (clientip != null) emit(clientip);
  9. """
  10. }
  11. }
  12. }

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

Python

  1. resp = client.search(
  2. index="my-index",
  3. runtime_mappings={
  4. "http.clientip": {
  5. "type": "ip",
  6. "script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
  7. }
  8. },
  9. query={
  10. "match": {
  11. "http.clientip": "40.135.0.0"
  12. }
  13. },
  14. fields=[
  15. "http.clientip"
  16. ],
  17. )
  18. print(resp)

Js

  1. const response = await client.search({
  2. index: "my-index",
  3. runtime_mappings: {
  4. "http.clientip": {
  5. type: "ip",
  6. script:
  7. "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ",
  8. },
  9. },
  10. query: {
  11. match: {
  12. "http.clientip": "40.135.0.0",
  13. },
  14. },
  15. fields: ["http.clientip"],
  16. });
  17. console.log(response);

Console

  1. GET my-index/_search
  2. {
  3. "runtime_mappings": {
  4. "http.clientip": {
  5. "type": "ip",
  6. "script": """
  7. String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
  8. if (clientip != null) emit(clientip);
  9. """
  10. }
  11. },
  12. "query": {
  13. "match": {
  14. "http.clientip": "40.135.0.0"
  15. }
  16. },
  17. "fields" : ["http.clientip"]
  18. }

Return calculated results

http.clientipランタイムフィールドを使用して、特定のIPアドレスを検索するためのシンプルなクエリを定義し、関連するすべてのフィールドを返すことができます。fieldsパラメータは、_search APIのすべてのフィールドに対して機能し、元の_sourceの一部として送信されなかったフィールドでも機能します:

Python

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

Ruby

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

Js

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

Console

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

レスポンスには、検索クエリで指定された特定のIPアドレスが含まれています。Painlessスクリプト内のgrokパターンは、ランタイム中にmessageフィールドからこの値を抽出しました。

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" : "1iN2a3kBw4xTzEDqyYE0",
  12. "_score" : 1.0,
  13. "_source" : {
  14. "timestamp" : "2020-04-30T14:30:17-05:00",
  15. "message" : "40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"
  16. },
  17. "fields" : {
  18. "http.clientip" : [
  19. "40.135.0.0"
  20. ]
  21. }
  22. }
  23. ]
  24. }
  25. }