Изучение данных с помощью полей во время выполнения
Рассмотрим большой набор данных журналов, из которых необходимо извлечь поля. Индексирование данных занимает много времени и использует много дискового пространства, а вам нужно просто изучить структуру данных, не привязываясь к схеме заранее.
Вы знаете, что ваши данные журналов содержат определённые поля, которые вы хотите извлечь. В данном случае мы сосредоточимся на полях @timestamp и message. С помощью полей во время выполнения вы можете определять скрипты для расчёта значений во время поиска для этих полей.
Определение индексированных полей в качестве отправной точки
Вы можете начать с простого примера, добавив поля @timestamp и message в отображение my-index-000001 в качестве индексированных полей. Для сохранения гибкости используйте тип поля wildcard для поля message:
PUT /my-index-000001/
{
"mappings": {
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
}
} Загрузка данных
После сопоставления полей, которые вы хотите получить, индексируйте несколько записей из ваших данных журналов в Elasticsearch. Следующий запрос использует API массовой загрузки, чтобы индексировать необработанные данные журналов в my-index-000001. Вместо индексирования всех ваших данных журналов вы можете использовать небольшую выборку для экспериментов с полями во время выполнения.
Окончательный документ не является допустимым форматом Apache log, но мы можем учесть этот случай в нашем скрипте.
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 хранит ваши необработанные данные.
GET /my-index-000001
Отображение содержит два поля: @timestamp и message.
{
"my-index-000001" : {
"aliases" : { },
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_second"
},
"message" : {
"type" : "wildcard"
},
"timestamp" : {
"type" : "date"
}
}
},
...
}
} Определение поля во время выполнения с помощью шаблона grok
Если вы хотите получить результаты, которые включают clientip, вы можете добавить это поле в качестве поля во время выполнения в отображении. Следующий скрипт во время выполнения определяет шаблон grok, который извлекает структурированные поля из одного текстового поля внутри документа. Шаблон grok похож на регулярное выражение, которое поддерживает алиасированные выражения, которые вы можете повторно использовать.
Скрипт соответствует шаблону журнала %{COMMONAPACHELOG}, который понимает структуру журналов Apache. Если шаблон соответствует, скрипт выводит значение соответствующего IP-адреса. Если шаблон не соответствует (clientip != null), скрипт просто возвращает значение поля без аварийного завершения.
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 в ваших отображениях индексов, но только в контексте этого конкретного поиска:
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 только один раз и можете вернуть несколько значений:
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-адресу
Используя поле во время выполнения http.clientip, вы можете определить простой запрос для поиска по определённому IP-адресу и возврата всех связанных полей.
GET my-index-000001/_search
{
"query": {
"match": {
"http.clientip": "40.135.0.0"
}
},
"fields" : ["*"]
} API возвращает следующий результат. Поскольку http является полем во время выполнения composite, ответ включает каждое из подполей под fields, включая любые связанные значения, которые соответствуют запросу. Без предварительного построения структуры данных вы можете искать и изучать свои данные осмысленными способами, чтобы проводить эксперименты и определять, какие поля индексировать.
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_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 в скрипте?
if (clientip != null) emit(clientip);
Если скрипт не включал это условие, запрос завершился бы ошибкой на любом фрагменте, который не соответствует шаблону. Включив это условие, запрос пропускает данные, которые не соответствуют шаблону grok.
Поиск документов в определённом диапазоне
Вы также можете выполнить запрос диапазона, который работает с полем timestamp. Следующий запрос возвращает все документы, где поле timestamp больше или равно 2020-04-30T14:31:27-05:00:
GET my-index-000001/_search
{
"query": {
"range": {
"timestamp": {
"gte": "2020-04-30T14:31:27-05:00"
}
}
}
} Ответ включает документ, где формат журнала не соответствует, но метка времени попадает в определённый диапазон.
{
...
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_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",
"_type" : "_doc",
"_id" : "htEhyncBRSB6iD-PoBqe",
"_score" : 1.0,
"_source" : {
"timestamp" : "2020-04-30T14:31:28-05:00",
"message" : "not a valid apache log"
}
}
]
}
} Определение поля во время выполнения с помощью шаблона dissect
Если вам не нужна мощность регулярных выражений, вы можете использовать шаблоны dissect вместо шаблонов grok. Шаблоны dissect соответствуют фиксированным разделителям, но, как правило, быстрее, чем grok.
Вы можете использовать dissect для достижения тех же результатов, что и при разборе журналов Apache с помощью шаблона grok. Вместо соответствия шаблону журнала вы включаете части строки, которые вы хотите отбросить. Особое внимание к частям строки, которые вы хотите отбросить, поможет создать успешные шаблоны dissect.
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);
"""
}
}
} Аналогично, вы можете определить шаблон dissect для извлечения кода ответа HTTP:
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, используя поле во время выполнения http.responses. Используйте параметр fields запроса _search, чтобы указать, какие поля вы хотите получить:
GET my-index-000001/_search
{
"query": {
"match": {
"http.responses": "304"
}
},
"fields" : ["http.client_ip","timestamp","http.verb"]
} В ответе содержится один документ, где код HTTP равен 304:
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index-000001",
"_type" : "_doc",
"_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"
]
}
}
]
}
}
© 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/runtime-examples.html