深入理解 grok
编辑深入理解 grok编辑
Grok 是一种正则表达式方言,支持可重用的别名表达式。Grok 非常适用于 syslog 日志、Apache 和其他 Web 服务器日志、MySQL 日志,以及通常为人类而非计算机编写的任何日志格式。
Grok 基于 Oniguruma 正则表达式库,因此任何正则表达式在 grok 中都是有效的。Grok 使用这种正则表达式语言来允许命名现有模式并将它们组合成更复杂的模式来匹配您的字段。
Grok 模式编辑
Elastic Stack 附带了许多 预定义的 grok 模式,简化了 grok 的使用。重用 grok 模式的语法采用以下形式之一
|
|
|
-
SYNTAX
- 将与您的文本匹配的模式的名称。例如,
NUMBER
和IP
都是默认模式集中提供的模式。NUMBER
模式匹配3.44
之类的数据,而IP
模式匹配55.3.244.1
之类的数据。 -
ID
- 您赋予要匹配的文本片段的标识符。例如,
3.44
可以是事件的持续时间,因此您可以将其称为duration
。字符串55.3.244.1
可以标识发出请求的client
。 -
TYPE
- 您要将命名字段强制转换成的的数据类型。支持的类型有
int
、long
、double
、float
和boolean
。
例如,假设您有如下所示的消息数据
3.44 55.3.244.1
第一个值是一个数字,后跟一个看起来像 IP 地址的内容。您可以使用以下 grok 表达式来匹配此文本
%{NUMBER:duration} %{IP:client}
迁移到 Elastic 通用模式 (ECS)编辑
为了简化迁移到 Elastic 通用模式 (ECS),除了现有模式外,还提供了一组新的符合 ECS 的模式。新的 ECS 模式定义捕获符合该模式的事件字段名称。
ECS 模式集包含旧模式集中的所有模式定义,并且可以直接替换。使用 ecs-compatability
设置来切换模式。
新功能和增强功能将添加到符合 ECS 的文件中。旧模式可能仍然会收到向后兼容的错误修复。
在 Painless 脚本中使用 grok 模式编辑
您可以将预定义的 grok 模式合并到 Painless 脚本中以提取数据。要测试您的脚本,请使用 Painless execute API 的 字段上下文 或创建一个包含该脚本的运行时字段。运行时字段提供了更大的灵活性并接受多个文档,但如果您在测试脚本的集群上没有写入权限,则 Painless execute API 是一个很好的选择。
如果您在构建 grok 模式以匹配数据方面需要帮助,请使用 Kibana 中的 Grok 调试器 工具。
例如,如果您正在使用 Apache 日志数据,则可以使用 %{COMMONAPACHELOG}
语法,该语法可以理解 Apache 日志的结构。示例文档可能如下所示
"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"
要从 message
字段中提取 IP 地址,您可以编写一个包含 %{COMMONAPACHELOG}
语法的 Painless 脚本。您可以使用 Painless execute API 的 ip
字段上下文 来测试此脚本,但让我们改用运行时字段。
根据示例文档,索引 @timestamp
和 message
字段。为了保持灵活性,请使用 wildcard
作为 message
的字段类型
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
PUT /my-index/ { "mappings": { "properties": { "@timestamp": { "format": "strict_date_optional_time||epoch_second", "type": "date" }, "message": { "type": "wildcard" } } } }
接下来,使用 批量 API 将一些日志数据索引到 my-index
中。
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
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"}
在运行时字段中合并 grok 模式和脚本编辑
现在,您可以在映射中定义一个包含您的 Painless 脚本和 grok 模式的运行时字段。如果模式匹配,则脚本会发出匹配 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); """ } } }
或者,您可以在搜索请求的上下文中定义相同的运行时字段。运行时定义和脚本与之前在索引映射中定义的完全相同。只需将该定义复制到 runtime_mappings
部分下的搜索请求中,并包含一个与运行时字段匹配的查询。此查询返回的结果与您在索引映射中 定义了针对 http.clientip
运行时字段的搜索查询的结果相同,但仅限于此特定搜索的上下文中
GET my-index/_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"] }
返回计算结果编辑
使用 http.clientip
运行时字段,您可以定义一个简单的查询来运行针对特定 IP 地址的搜索并返回所有相关字段。_search
API 上的 fields
参数适用于所有字段,即使是那些未作为原始 _source
的一部分发送的字段
response = client.search( index: 'my-index', body: { query: { match: { 'http.clientip' => '40.135.0.0' } }, fields: [ 'http.clientip' ] } ) puts response
GET my-index/_search { "query": { "match": { "http.clientip": "40.135.0.0" } }, "fields" : ["http.clientip"] }
响应包括搜索查询中指示的特定 IP 地址。Painless 脚本中的 grok 模式在运行时从 message
字段中提取了此值。
{ "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "my-index", "_id" : "1iN2a3kBw4xTzEDqyYE0", "_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" ] } } ] } }