Изучение данных с помощью полей выполнения
Рассмотрим большой набор данных логов, из которых необходимо извлечь поля. Индексирование данных занимает много времени и использует много места на диске, и вам нужно только изучить структуру данных, не привязываясь к схеме.
Вам известно, что ваши данные логов содержат определённые поля, которые нужно извлечь. В данном случае, мы сосредоточимся на полях @timestamp и message. С помощью полей выполнения вы можете определить скрипты для вычисления значений во время поиска для этих полей.
Определите индексированные поля в качестве отправной точки
Вы можете начать с простого примера, добавив поля @timestamp и message в отображение my-index-000001 в качестве индексированных полей. Чтобы оставаться гибкими, используйте wildcard в качестве типа поля для message:
resp = client.indices.create(
index="my-index-000001",
mappings={
"properties": {
"@timestamp": {
"format": "strict_date_optional_time||epoch_second",
"type": "date"
},
"message": {
"type": "wildcard"
}
}
},
)
print(resp) response = client.indices.create(
index: 'my-index-000001',
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-000001",
mappings: {
properties: {
"@timestamp": {
format: "strict_date_optional_time||epoch_second",
type: "date",
},
message: {
type: "wildcard",
},
},
},
});
console.log(response); 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, но мы можем учесть эту ситуацию в нашем скрипте.
resp = client.bulk(
index="my-index-000001",
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-000001',
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-000001",
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-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 хранит ваши необработанные данные.
resp = client.indices.get(
index="my-index-000001",
)
print(resp) response = client.indices.get( index: 'my-index-000001' ) puts response
const response = await client.indices.get({
index: "my-index-000001",
});
console.log(response); 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. Если шаблон соответствует (clientip != null), скрипт выводит значение соответствующего IP-адреса. Если шаблон не соответствует, скрипт просто возвращает значение поля без аварийного завершения.
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.client_ip": {
"type": "ip",
"script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip); \n "
}
},
)
print(resp) const response = await client.indices.putMapping({
index: "my-index-000001",
runtime: {
"http.client_ip": {
type: "ip",
script:
"\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip); \n ",
},
},
});
console.log(response); 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 в ваших отображениях индекса, но только в контексте этого конкретного поиска:
resp = client.search(
index="my-index-000001",
runtime_mappings={
"http.clientip": {
"type": "ip",
"script": "\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
}
},
query={
"match": {
"http.clientip": "40.135.0.0"
}
},
fields=[
"http.clientip"
],
)
print(resp) const response = await client.search({
index: "my-index-000001",
runtime_mappings: {
"http.clientip": {
type: "ip",
script:
"\n String clientip=grok('%{COMMONAPACHELOG}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ",
},
},
query: {
match: {
"http.clientip": "40.135.0.0",
},
},
fields: ["http.clientip"],
});
console.log(response); 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 только один раз и можно вернуть несколько значений:
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http": {
"type": "composite",
"script": "emit(grok(\"%{COMMONAPACHELOG}\").extract(doc[\"message\"].value))",
"fields": {
"clientip": {
"type": "ip"
},
"verb": {
"type": "keyword"
},
"response": {
"type": "long"
}
}
}
},
)
print(resp) response = client.indices.put_mapping(
index: 'my-index-000001',
body: {
runtime: {
http: {
type: 'composite',
script: 'emit(grok("%<COMMONAPACHELOG>s").extract(doc["message"].value))',
fields: {
clientip: {
type: 'ip'
},
verb: {
type: 'keyword'
},
response: {
type: 'long'
}
}
}
}
}
)
puts response const response = await client.indices.putMapping({
index: "my-index-000001",
runtime: {
http: {
type: "composite",
script: 'emit(grok("%{COMMONAPACHELOG}").extract(doc["message"].value))',
fields: {
clientip: {
type: "ip",
},
verb: {
type: "keyword",
},
response: {
type: "long",
},
},
},
},
});
console.log(response); 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-адресу и возврата всех связанных полей.
resp = client.search(
index="my-index-000001",
query={
"match": {
"http.clientip": "40.135.0.0"
}
},
fields=[
"*"
],
)
print(resp) const response = await client.search({
index: "my-index-000001",
query: {
match: {
"http.clientip": "40.135.0.0",
},
},
fields: ["*"],
});
console.log(response); 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",
"_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:
resp = client.search(
index="my-index-000001",
query={
"range": {
"timestamp": {
"gte": "2020-04-30T14:31:27-05:00"
}
}
},
)
print(resp) const response = await client.search({
index: "my-index-000001",
query: {
range: {
timestamp: {
gte: "2020-04-30T14:31:27-05:00",
},
},
},
});
console.log(response); 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",
"_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",
"_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.
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.client.ip": {
"type": "ip",
"script": "\n String clientip=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}').extract(doc[\"message\"].value)?.clientip;\n if (clientip != null) emit(clientip);\n "
}
},
)
print(resp) const response = await client.indices.putMapping({
index: "my-index-000001",
runtime: {
"http.client.ip": {
type: "ip",
script:
'\n String clientip=dissect(\'%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{status} %{size}\').extract(doc["message"].value)?.clientip;\n if (clientip != null) emit(clientip);\n ',
},
},
});
console.log(response); 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:
resp = client.indices.put_mapping(
index="my-index-000001",
runtime={
"http.responses": {
"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-000001",
runtime: {
"http.responses": {
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-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, чтобы указать, какие поля вы хотите получить:
resp = client.search(
index="my-index-000001",
query={
"match": {
"http.responses": "304"
}
},
fields=[
"http.client_ip",
"timestamp",
"http.verb"
],
)
print(resp) response = client.search(
index: 'my-index-000001',
body: {
query: {
match: {
'http.responses' => '304'
}
},
fields: [
'http.client_ip',
'timestamp',
'http.verb'
]
}
)
puts response const response = await client.search({
index: "my-index-000001",
query: {
match: {
"http.responses": "304",
},
},
fields: ["http.client_ip", "timestamp", "http.verb"],
});
console.log(response); 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",
"_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/8.17/runtime-examples.html