背景
近日云上Elasticsearch的客户,提过来了一个需求,期望把上游业务的日志中的一些无用字段从ES中删除掉,这些字段的名称是固定位数的随机字符串,期望能够通过一些过滤规则删除掉这些字段。
实现方案
日志内容的示例如下:
{
"req":{
"headers":{
"host":"x.x.x.x",
"connection":"keep-alive",
"x-forwarded-for":"x.x.x.x",
"x-forwarded-proto":"http",
"x-forwarded-host":"x.com",
"x-forwarded-port":"0000",
"x-forwarded-path":"x",
"x-forwarded-prefix":"y",
"et55ouqyzettcs070zr6fq74yknotwz":"abc",
"uuid":"xxxxx",
"vn4qhj6ienxegvhxyf56u8wq4w6icc9":"abc"
}
}
}
客户的需求是要删除上述示例中字段名为一串随机字符串的字段,示例中有两个,实际会有更多,并且不同类型的文档,字段名还不一样。
为了实现客户的需求,首先想到的就是是使用ES的Remove Ingest Processor去删除字段,但是发现Remove Ingest Processor中的field字段不支持正则表达式,只好作罢。
通过跟客户沟通,了解到要保留的字段是确定的,因此想到可以使用Set Ingest Processor通过复制原Object字段中需要保留的字段内容到一个新的Object类型的字段中,之后再删除原Object字段。
{
"processors":[
{
"set":{
"field":"req.newHeaders.host",
"copy_from":"req.headers.host"
}
},
{
"set":{
"field":"req.newHeaders.connection",
"copy_from":"req.headers.connection"
}
},
{
"remove":{
"field":"req.headers"
}
},
{
"set":{
"field":"req.headers",
"copy_from":"req.newHeaders"
}
},
{
"remove":{
"field":"req.newHeaders"
}
}
]
}
实际应用中,客户表示要保留的字段有几十个,把所有要保留的字段都set一遍会比较麻烦,后面有新增的字段,还需要再次修改ingest pipeline,期望能够比较简单易用的方式解决这个问题。
另外客户表示,要删除的字段不会太多,并且字段名称都是由31个随机字符串组成,且值都为"abc",看能不能根据这个条件删除掉不需要的字段。
在寻找解决方案的过程中,发现Logstash有类似的filter: Prune Filter Plugin, 可以直接根据字段值删除字段或者根据字段名的正则表达式删除字段:
filter {
prune {
blacklist_values => [ "abc"]
}
}
filter {
prune {
blacklist_names => [ "^([a-z]|[0-9]){31}$"]
}
}
然而客户因为没有用到Logstash,不希望单单为了去除一些无用的字段而新增一个组件,因此只能从ES的ingest pipeline入手解决。
既然ES的Remove Processor不支持通过正则表达式删除字段,也不支持通过字段的值来删除字段,那就只能使用Script Processor,通过脚本来删除了:
"processors": [
{
"script": {
"description": "Remove useless fields from 'req.headers' field",
"lang": "painless",
"source": """
Map map = (HashMap)ctx['req']['headers'];
Map headersMap = new HashMap();
for (entry in map.entrySet()){
if (entry.getValue()!= params.value){
headersMap.put(entry.getKey(), entry.getValue());
}
}
ctx['req']['headers'] = headersMap
""",
"params": {
"value": "abc"
}
}
}
]
使用Painless脚本,把每个文档中的req.headers字段解析成map,然后遍历,使用一个新的map保存值不是"abc"的字段,然后再赋值给req.headers。经过验证,实际是可行的。
通过使用上述Script Processor, 最终实现了使用较少的配置,实现了通过字段的值来删除字段的功能,满足了客户的需求。