使用运行时字段探索数据
编辑使用运行时字段探索数据编辑
假设您有一组大型日志数据,您想从中提取字段。对数据建立索引非常耗时,并且会占用大量磁盘空间,而您只想探索数据结构,而无需预先提交架构。
您知道您的日志数据包含您要提取的特定字段。在这种情况下,我们希望关注 @timestamp
和 message
字段。通过使用运行时字段,您可以定义脚本以在搜索时计算这些字段的值。
将索引字段定义为起点编辑
您可以从一个简单的示例开始,方法是将 @timestamp
和 message
字段作为索引字段添加到 my-index-000001
映射中。为了保持灵活性,请使用 wildcard
作为 message
的字段类型。
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
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 日志格式,但我们可以在脚本中考虑到这种情况。
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
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
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 地址的值。如果模式不匹配,则脚本仅返回字段值而不崩溃。
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 模式,并且可以返回多个值。
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
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", "_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", "_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 来获得与使用 grok 模式 解析 Apache 日志相同的结果。您需要包含要丢弃的字符串部分,而不是匹配日志模式。特别注意要丢弃的字符串部分将有助于构建成功的 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.responses
运行时字段检索特定的 HTTP 响应。使用 _search
请求的 fields
参数来指示要检索哪些字段。
response = client.search( index: 'my-index-000001', body: { query: { match: { 'http.responses' => '304' } }, fields: [ 'http.client_ip', 'timestamp', 'http.verb' ] } ) puts 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" ] } } ] } }