Разобщение данных
Dissect сопоставляет одно текстовое поле с определенным шаблоном. Шаблон dissect определяется частями строки, которые вы хотите отбросить. Обращение особого внимания на каждую часть строки поможет создать успешные шаблоны dissect.
Если вам не нужна мощь регулярных выражений, используйте шаблоны dissect вместо grok. Dissect использует гораздо более простой синтаксис, чем grok, и, как правило, работает быстрее. Синтаксис dissect прозрачен: сообщите dissect, что вам нужно, и он вернёт вам эти результаты.
Шаблоны dissect
Шаблоны dissect состоят из переменных и разделителей. Всё, что определено знаком процента и фигурными скобками %{}, считается переменной, например, %{clientip}. Вы можете назначить переменные любой части данных в поле и затем вернуть только нужные части. Разделителями являются любые значения между переменными, которые могут быть пробелами, дефисами или другими разделителями.
Например, предположим, что у вас есть данные логов с полем message, которое выглядит так:
"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}. Следующие два символа — дефисы с пробелами по обеим сторонам. Вы можете назначить переменную для каждого дефиса или одну переменную для представления дефисов и пробелов. Далее идёт набор скобок, содержащих отметку времени. Скобки являются разделителями, поэтому их нужно включить в шаблон dissect. На данный момент данные и соответствующий шаблон dissect выглядят так:
247.37.0.0 - - [30/Apr/2020:14:31:22 -0500]
%{clientip} %{ident} %{auth} [%{@timestamp}] | Первые части данных из поля | |
| Шаблон dissect для сопоставления с выбранными частями данных |
Используя тот же принцип, вы можете создать переменные для оставшихся частей данных. Двойные кавычки являются разделителями, поэтому их нужно включать в ваш шаблон dissect. Шаблон заменяет GET на переменную %{verb}, но оставляет HTTP как часть шаблона.
\"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0
"%{verb} %{request} HTTP/%{httpversion}" %{response} %{size} Объединение двух шаблонов приводит к шаблону dissect, который выглядит так:
%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size} Теперь, когда у вас есть шаблон dissect, как его проверить и использовать?
Проверка шаблонов dissect с помощью Painless
Вы можете включить шаблоны dissect в сценарии Painless для извлечения данных. Для проверки сценария используйте либо контексты полей API Painless execute, либо создайте поле runtime, включающее сценарий. Поля runtime предлагают большую гибкость и принимают несколько документов, но API Painless execute является отличным вариантом, если у вас нет прав записи на кластере, где вы тестируете сценарий.
Например, проверьте свой шаблон dissect с помощью API Painless execute, включив в него ваш сценарий Painless и один документ, соответствующий вашим данным. Начните с индексирования поля message как типа данных wildcard:
resp = client.indices.create(
index="my-index",
mappings={
"properties": {
"message": {
"type": "wildcard"
}
}
},
)
print(resp) response = client.indices.create(
index: 'my-index',
body: {
mappings: {
properties: {
message: {
type: 'wildcard'
}
}
}
}
)
puts response const response = await client.indices.create({
index: "my-index",
mappings: {
properties: {
message: {
type: "wildcard",
},
},
},
});
console.log(response); PUT my-index
{
"mappings": {
"properties": {
"message": {
"type": "wildcard"
}
}
}
} Если вам нужно получить код HTTP-ответа, добавьте свой шаблон dissect в сценарий Painless, который извлекает значение response. Для извлечения значений из поля используйте эту функцию:
`.extract(doc["<field_name>"].value)?.<field_value>`
В этом примере message — это <field_name>, а response — это <field_value>:
resp = client.scripts_painless_execute(
script={
"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 "
},
context="long_field",
context_setup={
"index": "my-index",
"document": {
"message": "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"
}
},
)
print(resp) const response = await client.scriptsPainlessExecute({
script: {
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 ',
},
context: "long_field",
context_setup: {
index: "my-index",
document: {
message:
'247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0',
},
},
});
console.log(response); POST /_scripts/painless/_execute
{
"script": {
"source": """
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));
"""
},
"context": "long_field",
"context_setup": {
"index": "my-index",
"document": {
"message": """247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0"""
}
}
} | Для полей runtime требуется метод | |
| Так как код ответа — целое число, используйте контекст | |
| Включите пример документа, соответствующий вашим данным. |
Результат включает код HTTP-ответа:
{
"result" : [
304
]
} Использование шаблонов dissect и сценариев в полях runtime
Если у вас есть функциональный шаблон dissect, вы можете добавить его в поле runtime для обработки данных. Поскольку поля runtime не требуют индексирования полей, у вас есть невероятная гибкость в изменении вашего сценария и его функциональности. Если вы уже проверили свой шаблон dissect с помощью API Painless execute, вы можете использовать этот тот же самый сценарий Painless в своём поле runtime.
Для начала добавьте поле message как тип wildcard, как в предыдущем разделе, но также добавьте @timestamp как date, если вы хотите использовать это поле для других случаев использования, например, в других сценариях:
resp = client.indices.create(
index="my-index",
mappings={
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
},
)
print(resp) response = client.indices.create(
index: 'my-index',
body: {
mappings: {
properties: {
"@timestamp": {
format: 'strict_date_optional_time||epoch_second',
type: 'date'
},
message: {
type: 'wildcard'
}
}
}
}
)
puts response const response = await client.indices.create({
index: "my-index",
mappings: {
properties: {
"@timestamp": {
format: "strict_date_optional_time||epoch_second",
type: "date",
},
message: {
type: "wildcard",
},
},
},
});
console.log(response); PUT /my-index/
{
"mappings": {
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
}
} Если вы хотите извлечь код HTTP-ответа с помощью вашего шаблона dissect, вы можете создать поле runtime, такое как http.response:
resp = client.indices.put_mapping(
index="my-index",
runtime={
"http.response": {
"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) const response = await client.indices.putMapping({
index: "my-index",
runtime: {
"http.response": {
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); 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));
"""
}
}
} После сопоставления нужных полей проиндексируйте несколько записей из ваших данных логов в Elasticsearch. Следующий запрос использует API bulk для индексирования исходных данных логов в my-index:
resp = client.bulk(
index="my-index",
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) response = client.bulk(
index: 'my-index',
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 const response = await client.bulk({
index: "my-index",
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); POST /my-index/_bulk?refresh=true
{"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"} Вы можете определить простой запрос для поиска определенного HTTP-ответа и возврата всех связанных полей. Используйте параметр fields API поиска, чтобы получить поле runtime http.response.
resp = client.search(
index="my-index",
query={
"match": {
"http.response": "304"
}
},
fields=[
"http.response"
],
)
print(resp) response = client.search(
index: 'my-index',
body: {
query: {
match: {
'http.response' => '304'
}
},
fields: [
'http.response'
]
}
)
puts response const response = await client.search({
index: "my-index",
query: {
match: {
"http.response": "304",
},
},
fields: ["http.response"],
});
console.log(response); GET my-index/_search
{
"query": {
"match": {
"http.response": "304"
}
},
"fields" : ["http.response"]
} В качестве альтернативы, вы можете определить то же самое поле runtime в контексте запроса поиска. Определение runtime и сценарий будут точно такими же, как определенные ранее в сопоставлении индекса. Просто скопируйте это определение в запрос поиска в раздел runtime_mappings и включите запрос, который соответствует полю runtime. Этот запрос вернёт те же результаты, что и предыдущий запрос поиска для поля runtime http.response в вашем сопоставлении индекса, но только в контексте этого конкретного поиска:
resp = client.search(
index="my-index",
runtime_mappings={
"http.response": {
"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 "
}
},
query={
"match": {
"http.response": "304"
}
},
fields=[
"http.response"
],
)
print(resp) const response = await client.search({
index: "my-index",
runtime_mappings: {
"http.response": {
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 ',
},
},
query: {
match: {
"http.response": "304",
},
},
fields: ["http.response"],
});
console.log(response); GET my-index/_search
{
"runtime_mappings": {
"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));
"""
}
},
"query": {
"match": {
"http.response": "304"
}
},
"fields" : ["http.response"]
} {
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "my-index",
"_id" : "D47UqXkBByC8cgZrkbOm",
"_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
]
}
}
]
}
}
© 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/8.17/dissect.html