Извлечение полей
Цель извлечения полей проста: у вас есть поля в ваших данных со множеством информации, но вы хотите извлечь только отдельные части.
Есть два варианта:
- Grok — это диалект регулярных выражений, поддерживающий алиасированные выражения, которые можно повторно использовать. Поскольку Grok работает поверх регулярных выражений (regex), любые регулярные выражения также допустимы в grok.
- Dissect извлекает структурированные поля из текста, используя разделители для определения шаблона сопоставления. В отличие от grok, dissect не использует регулярные выражения.
Начнём с простого примера, добавив поля @timestamp и message к отображению my-index в качестве индексированных полей. Для большей гибкости используйте wildcard в качестве типа поля для message:
PUT /my-index/
{
"mappings": {
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
}
} После отображения полей, которые вы хотите получить, проиндексируйте несколько записей из ваших логов в Elasticsearch. Следующий запрос использует API массовой загрузки для индексирования исходных данных логов в my-index. Вместо индексирования всех данных логов вы можете использовать небольшой образец для экспериментов с полями runtime.
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"} Извлечение IP-адреса из сообщения лога (Grok)
Если вы хотите получить результаты, включающие clientip, вы можете добавить это поле в качестве поля runtime в отображении. Следующий скрипт runtime определяет шаблон grok, который извлекает структурированные поля из поля message.
Скрипт соответствует шаблону логов %{COMMONAPACHELOG}, который понимает структуру логов Apache. Если шаблон соответствует, скрипт возвращает значение, соответствующее IP-адресу. Если шаблон не соответствует (clientip != null), скрипт просто возвращает значение поля без сбоя.
PUT my-index/_mappings
{
"runtime": {
"http.clientip": {
"type": "ip",
"script": """
String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;
if (clientip != null) emit(clientip);
"""
}
}
} | Это условие гарантирует, что скрипт ничего не возвращает, даже если шаблон сообщения не соответствует. |
Вы можете определить простой запрос для поиска определённого IP-адреса и возвращения всех связанных полей. Используйте параметр fields API поиска для получения поля runtime http.clientip.
GET my-index/_search
{
"query": {
"match": {
"http.clientip": "40.135.0.0"
}
},
"fields" : ["http.clientip"]
} Ответ включает документы, где значение для http.clientip соответствует 40.135.0.0.
{
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_type" : "_doc",
"_id" : "Rq-ex3gBA_A0V6dYGLQ7",
"_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"
]
}
}
]
}
} Разбор строки для извлечения части поля (Dissect)
Вместо сопоставления с шаблоном лога, как в предыдущем примере, вы можете просто определить шаблон dissect, чтобы включить части строки, которые вы хотите отбросить.
Например, данные логов в начале этого раздела включают поле message. Это поле содержит несколько данных:
"message" : "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
Вы можете определить шаблон dissect в поле runtime для извлечения кода ответа HTTP, который является 304 в предыдущем примере.
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));
"""
}
}
} Затем вы можете выполнить запрос для получения определённого кода ответа HTTP, используя поле runtime http.response:
GET my-index/_search
{
"query": {
"match": {
"http.response": "304"
}
},
"fields" : ["http.response"]
} Ответ включает один документ, где код ответа HTTP равен 304:
{
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_type" : "_doc",
"_id" : "Sq-ex3gBA_A0V6dYGLQ7",
"_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
]
}
}
]
}
} Разделение значений в поле по разделителю (Dissect)
Предположим, вы хотите извлечь часть поля, как в предыдущем примере, но вы хотите разделить по определённым значениям. Вы можете использовать шаблон dissect для извлечения только необходимой информации и возвращения данных в определённом формате.
Например, предположим, у вас есть данные логов сборки мусора (gc) из Elasticsearch в этом формате:
[2021-04-27T16:16:34.699+0000][82460][gc,heap,exit] class space used 266K, capacity 384K, committed 384K, reserved 1048576K
Вы хотите извлечь used, capacity и committed данные вместе с соответствующими значениями. Давайте добавим несколько документов с данными логов для использования в качестве примера:
POST /my-index/_bulk?refresh
{"index":{}}
{"gc": "[2021-04-27T16:16:34.699+0000][82460][gc,heap,exit] class space used 266K, capacity 384K, committed 384K, reserved 1048576K"}
{"index":{}}
{"gc": "[2021-03-24T20:27:24.184+0000][90239][gc,heap,exit] class space used 15255K, capacity 16726K, committed 16844K, reserved 1048576K"}
{"index":{}}
{"gc": "[2021-03-24T20:27:24.184+0000][90239][gc,heap,exit] Metaspace used 115409K, capacity 119541K, committed 120248K, reserved 1153024K"}
{"index":{}}
{"gc": "[2021-04-19T15:03:21.735+0000][84408][gc,heap,exit] class space used 14503K, capacity 15894K, committed 15948K, reserved 1048576K"}
{"index":{}}
{"gc": "[2021-04-19T15:03:21.735+0000][84408][gc,heap,exit] Metaspace used 107719K, capacity 111775K, committed 112724K, reserved 1146880K"}
{"index":{}}
{"gc": "[2021-04-27T16:16:34.699+0000][82460][gc,heap,exit] class space used 266K, capacity 367K, committed 384K, reserved 1048576K"} Посмотрев на данные ещё раз, есть отметка времени, другие данные, которые вас не интересуют, а затем данные used, capacity и committed:
[2021-04-27T16:16:34.699+0000][82460][gc,heap,exit] class space used 266K, capacity 384K, committed 384K, reserved 1048576K
Вы можете назначить переменные каждой части данных в поле gc, а затем вернуть только необходимые части. Всё в фигурных скобках {} считается переменной. Например, переменные [%{@timestamp}][%{code}][%{desc}] будут соответствовать трём первым частям данных, все из которых находятся в квадратных скобках [].
[%{@timestamp}][%{code}][%{desc}] %{ident} used %{usize}, capacity %{csize}, committed %{comsize}, reserved %{rsize} Ваш шаблон dissect может содержать термины used, capacity и committed вместо использования переменных, потому что вы хотите вернуть эти термины точно. Вы также назначаете переменные значениям, которые вы хотите вернуть, например, %{usize}, %{csize} и %{comsize}. Разделитель в данных логов — запятая, поэтому ваш шаблон dissect также должен использовать этот разделитель.
Теперь, когда у вас есть шаблон dissect, вы можете включить его в скрипт Painless в качестве части поля runtime. Скрипт использует ваш шаблон dissect для разделения поля gc, а затем возвращает точно ту информацию, которую вы хотите, как определено методом emit. Поскольку dissect использует простой синтаксис, вам нужно только указать, что именно вы хотите.
Следующий шаблон сообщает dissect вернуть термин used, пробел, значение из gc.usize и запятую. Этот шаблон повторяется для других данных, которые вы хотите получить. Хотя этот шаблон может не быть полезным в производстве, он обеспечивает большую гибкость для экспериментов и манипулирования данными. В производственной среде вы, возможно, просто захотите использовать emit(gc.usize), а затем агрегировать по этому значению или использовать его в вычислениях.
emit("used" + ' ' + gc.usize + ', ' + "capacity" + ' ' + gc.csize + ', ' + "committed" + ' ' + gc.comsize) Совместив всё, вы можете создать поле runtime с именем gc_size в запросе поиска. Используя опцию fields, вы можете получить все значения поля runtime gc_size. Этот запрос также включает агрегацию по корзинам для группировки данных.
GET my-index/_search
{
"runtime_mappings": {
"gc_size": {
"type": "keyword",
"script": """
Map gc=dissect('[%{@timestamp}][%{code}][%{desc}] %{ident} used %{usize}, capacity %{csize}, committed %{comsize}, reserved %{rsize}').extract(doc["gc.keyword"].value);
if (gc != null) emit("used" + ' ' + gc.usize + ', ' + "capacity" + ' ' + gc.csize + ', ' + "committed" + ' ' + gc.comsize);
"""
}
},
"size": 1,
"aggs": {
"sizes": {
"terms": {
"field": "gc_size",
"size": 10
}
}
},
"fields" : ["gc_size"]
} Ответ включает данные из поля gc_size, отформатированные точно так, как вы его определили в шаблоне dissect!
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_type" : "_doc",
"_id" : "GXx3H3kBKGE42WRNlddJ",
"_score" : 1.0,
"_source" : {
"gc" : "[2021-04-27T16:16:34.699+0000][82460][gc,heap,exit] class space used 266K, capacity 384K, committed 384K, reserved 1048576K"
},
"fields" : {
"gc_size" : [
"used 266K, capacity 384K, committed 384K"
]
}
}
]
},
"aggregations" : {
"sizes" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "used 107719K, capacity 111775K, committed 112724K",
"doc_count" : 1
},
{
"key" : "used 115409K, capacity 119541K, committed 120248K",
"doc_count" : 1
},
{
"key" : "used 14503K, capacity 15894K, committed 15948K",
"doc_count" : 1
},
{
"key" : "used 15255K, capacity 16726K, committed 16844K",
"doc_count" : 1
},
{
"key" : "used 266K, capacity 367K, committed 384K",
"doc_count" : 1
},
{
"key" : "used 266K, capacity 384K, committed 384K",
"doc_count" : 1
}
]
}
}
}
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/scripting-field-extraction.html