你真的了解Grok吗

在日志处理的过程中,有一项非常常见的任务就是把原始的单行日志转换成结构化的日志。如果你使用了ELK,那么你可以利用ES对数据进行聚合,使用Kibana来进行数据可视化从日志中来发现一些有价值的信息。

在LogStash中,这项工作是由logstash-filter-grok来完成的,它有超过200个可用的,大家都认为是比较有用的Grok模式,例如IPv6地址、UNIX路径等等。

下面是一个示例日志

2016-09-19T18:19:00 [8.8.8.8:prd] DEBUG this is an example log message

使用Grok库,我们可以很容易的就完成日志格式化提取的任务

%{TIMESTAMP_ISO8601:timestamp} \[%{IPV4:ip};%{WORD:environment}\] %{LOGLEVEL:log_level} %{GREEDYDATA:message}

提取后的数据格式如下

{
  "timestamp": "2016-09-19T18:19:00",
  "ip": "8.8.8.8",
  "environment": "prd",
  "log_level": "DEBUG",
  "message": "this is an example log message"
}

看起来这是一件非常简单的事情,好吧。。那这篇文章就这样写完了么,当然不是。。

为什么我的Grok使用起来非常的慢

这是一个非常常见的问题。性能这个问题通常都是要被拿出来讨论的,用户通常会发现使用了Grok表达式之后,LogStash处理日志的速度变得很慢。就像前面所说的一样,Grok模式是基于正则表达式的,所以这个插件在性能上已经对正则做了非常多的性能优化的了。接下来的章节,我们会讨论在使用Grok模式中需要注意的点

多做些性能测试

在设计Grok表达式的时候,我们需要一些方法来测试究竟哪种写法性能表现更好。出于这个原因,我些了个很小的jruby脚步用于测试Grok插件处理我所写的Grok模式的性能,你可以在这里获取到这个脚本

留意grok匹配失败的时候对性能的影响

尽管Grok匹配的性能是非常重要的,但是匹配失败的时候对性能的影响也是我们需要留意的。当grok匹配失败的时候,插件会为这个事件打个tag,默认是_grokparsefailure。LogStash允许你把这些处理失败的事件路由到其他地方做后续的处理,例如

input { # ... }
filter {
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{IPV4:ip};%{WORD:environment}\] %{LOGLEVEL:log_level} %{GREEDYDATA:message}" }
  }
}
output {
  if "_grokparsefailure" in [tags] {
    # write events that didn't match to a file
    file { "path" => "/tmp/grok_failures.txt" }
  } else {
     elasticsearch { }
  }
}

这样的话我们就可以对这些处理失败的事件做性能基准测试了。

现在,我们要开始对Apache的日志进行格式化处理了

220.181.108.96 - - [13/Jun/2015:21:14:28 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"

然后我们使用下面的Grok模式去进行格式化提取

%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}

然后我们使用三种示例日志去测试这个Grok的性能,和Grok不匹配的日志分别出现在开始,中间和结束的位置

# beginning mismatch - doesn't start with an IPORHOST
'tash-scale11x/css/fonts/Roboto-Regular.ttf HTTP/1.1" 200 41820 "http://semicomplete.com/presentations/logs'

# middle mismatch - instead of an HTTP verb like GET or PUT there's the number 111
'220.181.108.96 - - [13/Jun/2015:21:14:28 +0000] "111 /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"'

# end mismatch - the last element isn't a quoted string, but a number
'220.181.108.96 - - [13/Jun/2015:21:14:28 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" 1'

下面是性能测试的结果

Paste_Image.png

基于上面这个测试结果,我们可以发现,Grok的性能和不匹配的日志所出现的位置有关,最快与最慢的性能差了差不多6倍。这就能解释为什么有用户提出当Grok匹配日志失败的时候CPU会被吃满的原因了,例如这个issues
https://github.com/logstash-plugins/logstash-filter-grok/issues/37.

我们能做些什么呢

快速失败,设置锚点

我们已经知道了处理失败对grok的性能影响是非常大的,所以我们需要解决这个问题。对于正则引擎来说,你需要做的最合适的事情就是减少正则表达式所需要的猜测。这就是为什么贪婪匹配最好少用的原因,那回到这个问题,有没一种更好的方法来调整这个Grok模式呢,让我们重新来看看这行Apache的日志

220.181.108.96 - - [13/Jun/2015:21:14:28 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"

刚才我们使用的Grok模式是这样的

%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}

由于用户以为Grok表达式只会从开头匹配到结束,所以导致了在一些普通的场景下也会出现性能问题。但是实际上,Grok只是被告知“在这段文本中寻找匹配的内容”,这就意味着下面这种示例也会被Grok所匹配。。。

OMG OMG OMG EXTRA INFORMATION 220.181.108.96 - - [13/Jun/2015:21:14:28 +0000] "GET /blog/geekery/xvfb-firefox.html HTTP/1.1" 200 10975 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" OH LOOK EVEN MORE STUFF

呃。。这都行,不过解决这个问题还是很简单的,我们加一些锚点就搞定了。锚点可以让你再一个指定的位置处理字符串。加入了开始和结束的锚点之后(^和$),Grok就会从开头处理日志到结束了。这对处理那些不能匹配的日志有非常重要的作用。假如我们没有假如锚点,当正则无法匹配这行日志的时候,它就会开始从子字符串中进行匹配,然后性能就会下降,接下来我们把锚点加上,然后再做一次测试

^%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent}$
Paste_Image.png

可以看到性能有了很大的提升,在一开始就匹配失败的场景中,性能提升了将近10倍

留意匹配了两次的表达式

你可能会说,“好吧,我的日志都是能匹配通过的,没有上面的问题”,但是事情可能并不是这样的

我们看到过非常多的grok模式在处理同一个网关发出的多种应用日志时候所出现的问题,例如syslog。想象一下这样一个场景,我们使用了“common_header: payload“这种日志格式来记录了三种应用日志

Application 1: '8.8.8.8 process-name[666]: a b 1 2 a lot of text at the end'
Application 2: '8.8.8.8 process-name[667]: a 1 2 3 a lot of text near the end;4'
Application 3: '8.8.8.8 process-name[421]: a completely different format | 1111'

通常我们会在一个Grok里面就把三种日志都处理掉

grok {
  "match" => { "message => [
    '%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{WORD:word_1} %{WORD:word_2} %{NUMBER:number_1} %{NUMBER:number_2} %{DATA:data}',
    '%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{WORD:word_1} %{NUMBER:number_1} %{NUMBER:number_2} %{NUMBER:number_3} %{DATA:data};%{NUMBER:number_4}',
    '%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{DATA:data} | %{NUMBER:number}'
    ] }
}

值得留意的是即使你的日志是能正常匹配的,Grok还是会按照顺序许匹配送进来的日志,当碰到第一个匹配成功的日志就break掉这个循环。这就要我们自己去判断一下,怎么放是最合适的了,不然的话会一个一个往下进行尝试,毕竟是多种不同的格式。
一种常用的优化方案是使用分层匹配来对这个Grok进行优化

filter {
  grok {
    "match" => { "message" => '%{IPORHOST:clientip} %{DATA:process_name}\[%{NUMBER:process_id}\]: %{GREEDYDATA:message}' },
    "overwrite" => "message"
  }
  grok {
    "match" => { "message" => [
      '%{WORD:word_1} %{WORD:word_2} %{NUMBER:number_1} %{NUMBER:number_2} %{GREEDYDATA:data}',
      '%{WORD:word_1} %{NUMBER:number_1} %{NUMBER:number_2} %{NUMBER:number_3} %{DATA:data};%{NUMBER:number_4}',
      '%{DATA:data} | %{NUMBER:number}'
    ] }
  }
)

这是两种匹配方案的性能测试结果

Paste_Image.png

看起来真有意思。。使用锚点的话,无论哪种方案性能都是一样的。不用锚点的情况下分层Grok的方案比不分层的又快很多

那我们怎么知道我们所创建的Grok是合适的

我们已经得出了对_grokparsefaiure进行处理的必要性了,那么我们还能做什么呢?
从3.2.0这个Grok插件开始,它有一些参数可以帮助你了解为什么一个事件会被处理那么久了。使用timeout_millistag_on_timeout可以设置Grok匹配的最大处理时长。如果超时了,这个事件会被打上_groktimeout的tag,然后我们就可以把他们送到一个Grok处理失败的ES索引里面去做后续的分析了

另外一个很棒的方法是LogStash5.0带了插件性能统计的功能,我们可以通过API来查看插件处理日志的性能了

$ curl localhost:9600/_node/stats/pipeline?pretty | jq ".pipeline.plugins.filters"
[
  {
    "id": "grok_b61938f3833f9f89360b5fba6472be0ad51c3606-2",
    "events": {
      "duration_in_millis": 7,
      "in": 24,
      "out": 24
    },
    "failures": 24,
    "patterns_per_field": {
      "message": 1
    },
    "name": "grok"
  },
  {
    "id": "kv_b61938f3833f9f89360b5fba6472be0ad51c3606-3",
    "events": {
      "duration_in_millis": 2,
      "in": 24,
      "out": 24
    },
    "name": "kv"
  }
]

然后我们就可以通过duration_in_millis来判断一个插件的性能了

总结

希望这篇文章能帮你了解为什么Grok的性能会变得慢和如何去提升他的性能。下面是对这篇文字的总结:

  • Grok在匹配失败的时候性能可能并不那么好
  • 多留意_grokparsefailures出现的频率和出现时候的性能
  • 写正则的时候记得打锚点
  • 不使用锚点的时候分层Grok处理的性能会比不分层的性能好,不过打了锚点的话两个都一样
  • 多使用LogStash的性能监控功能,后续还可以拿来分析用

原文:https://www.elastic.co/blog/do-you-grok-grok

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,963评论 25 707
  • 读大学时,我们的校报总编,是我们这些学生记者最喜欢的老师,也是我们学校知名度很高的人。他长得很有特色,清瘦,戴眼镜...
    七默七默阅读 361评论 2 1
  • 游于艺顾文豪刊于2010年4月3日《新京报》 在云南丽江古城,离四方街不远的木府前,立着一座石牌坊,上有四个大字“...
    茸小呆阅读 2,300评论 0 1
  • 姓名:徐程程 公司.:海通食品集团有限公司 组别: 340期【反省一组】学员 【知~学习】 背诵《六项精进大纲》1...
    ting8596阅读 198评论 0 0