木讷大叔爱运维

Logstash之Grok正则匹配,让正则进阶!

Image

点击上方蓝色字体,关注我们

预计阅读时间3分钟Image

Image

简述

关于ELKStack日志收集Nginx日志,如果我们定义的Nginx格式使用JSON,那么Logstash可以非常轻松的按预定义好的JSON格式进行收集,这样就可以避免额外的 Filter/Grok 配置。但是如果在我们的生产环境中,Nginx日志格式在某些情况下并不是如此,那就不得不使用Logstash的Filter/Grok组件通过正则匹配过滤,下面我们就来演示下吧。

Nginx日志格式

为了帮助我们有效的理解grok的正则表达式,因此我特意将日志格式定义的复杂一些。

Google上好多案例都是套用的默认格式,为了便于大家以后更好的扩展使用,我在此并没有使用默认字段。

log_format main '$time_local - $upstream_addr $server_addr:$server_port '
'$request_method $uri $args '
'- $remote_addr $server_protocol [$http_user_agent] [$http_cookie] $http_referer '
'$host $status 0 0 $bytes_sent $request_length 0'
'"$upstream_cache_status" $request_time $upstream_response_time';

其对应的真实日志如下:

08/Jan/2016:08:27:43 +0800 - 10.10.6.212:8088 10.10.6.110:80 GET /vvv/test/stat/index proptype=11&level=2&srtype=2&city=dz&region=XJ&begindate=2016-01-08&enddate=2016-01-08&apiKey=c2c959b203d669a9a21861379cb4523c&test=2 - 10.10.6.10 HTTP/1.1 [Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36] [JSESSIONID=kq3v6xi2b74j1a9yxvfvcq131] http://10.10.6.110/test www.test.com 200 0 0 485 209 0"-" 1.642 1.642

Logstash正则表达式

Logstash中默认已存在一部分正则表达式供我们使用,在以下路径中我们可以看到:

ls /usr/local/logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-2.0.2/patterns
aws bacula bro exim firewalls grok-patterns haproxy java junos linux-syslog mcollective mcollective-patterns mongodb nagios postgresql rails redis ruby

其中最基本的定义是在grok-patterns中,但是其中的正则搭配并不适合我们本次Nginx日志格式,因此我希望通过自定义的规则来实现格式匹配,然后使用Grok组件通过patterns_dir来调用即可。

另外,我们可以通过“http://grokconstructor.appspot.com/do/match”或“http://grokdebug.herokuapp.com/”这两个站点来查看我们的正则是否正确。

下面我们来通过分解日志中的各个字段来解析正则表达式:

nginx日志字段定义nginx访问字段正则表达式
$time_local08/Jan/2016:08:27:43 +0800%{HTTPDATE:timestamp}
---
$upstream_addr10.10.6.212:8088%{HOSTPORT:upstream}
$server_addr:$server_port
10.10.6.110:80%{HOSTPORT:request_server}
$request_methodGET%{WORD:request_method}
$uri/vvv/test/stat/index%{URIPATH:uri}
$argsproptype=11&level=2&srtype=2&city=dz&region=XJ&begindate=2016-01-08&enddate=2016-01-08&apiKey=c2c959b203d669a9a21861379cb4523c&test=2%{URIPARAM1:args}
---
$remote_addr10.10.6.10%{IP:clientip}
$server_protocolHTTP/1.1HTTP/%{NUMBER:httpversion}
[$http_user_agent][Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36]\ [%{GREEDYDATA:agent}\ ]
[$http_cookie][JSESSIONID=kq3v6xi2b74j1a9yxvfvcq131]\ [%{GREEDYDATA:cookie}\ ]
$http_refererhttp://10.10.6.110/test(?:%{URI:referrer}|-)
$hostwww.test.com%{HOSTNAME:domain}
$status200%{NUMBER:status:int}
000
000
$bytes_sent485%{NUMBER:body_sent:int}
$request_length209%{NUMBER:request_length:int}
0"$upstream_cache_status"0"-"0"-"
$request_time1.642%{NUMBER:request_time:float}
$upstream_response_time1.642%{NUMBER:response_time:float}

正则表达式定义完毕后,我们可以到http://grokconstructor.appspot.com/do/match来验证。

「要点:」

  1. 上图中”URIPARAM1“ 是我们自定义的个一个正则,其规则为[A-Za-z0-9$.+!‘|(){},~@#%&/=:;_?-[]],由%{URIPARAM1:args}调用。它和默认URIPARAM规则是有区别的。如果你观察仔细的话,可以看到默认的“URIPARAM”是以?开头,而我们自定义的URIPARAM1不是。

注意:这个规则是更新的,可能由于版本的问题,将导致我们在使用默认正则时会出现“Longest prefix that matches”的提示,“after matche”会显示日志中未匹配的剩余内容。

  1. http_user_agent和http_cookie字段使用“http://grokdebug.herokuapp.com/patterns#”中“nginx-access”生成的规则“%{QS:agent}”以及类似“%{QS:cookie}”也会出现“Longest prefix that matches”提示,因此这两个正则是不正确的。我们可以使用%{GREEDYDATA:agent}和%{GREEDYDATA:cookie}代替,而“GREEDYDATA .*”,是匹配的所有字符。注意其和“QS %{QUOTEDSTRING}”的区别。

  2. ?:%{URI:referrer}|-)正则表示如果$referer字段为空,则用"-"表示,若不为空则显示referer的内容。经测试如果直接设置成%{URI:referrer},过滤时当referer为空时,会导致grokfailure,因此需要注意此字段的正则表达式。

注意:某些字段为“-”时,可能会导致grokfailure,此时我们可以通过(?:%{XX:XX}|-)的方式进行匹配,即为空时显示“-”。

❝

官方正则查询:https://github.com/kkos/oniguruma/blob/master/doc/RE

❞

Logstash调用

「1.在logstash中定义正则目录并定义规则」

mkdir -p /usr/local/logstash/patterns
vim /usr/local/logstash/patterns/nginx
URIPARAM1 [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
NGINXACCESS %{HTTPDATE:timestamp} - %{HOSTPORT:upstream} %{HOSTPORT:request_server} %{WORD:request_method} %{URIPATH:uri} %{URIPARAM1:args} - %{IP:clientip} HTTP/%{NUMBER:httpversion} \[%{GREEDYDATA:agent}\] \[%{GREEDYDATA:cookie}\] %{URI:referer} %{HOSTNAME:host} %{NUMBER:status:int} 0 0 %{NUMBER:body_sent:int} %{NUMBER:request_length:int} 0\"-\" %{NUMBER:request_time:float} %{NUMBER:response_time:float}

其中:

  • URIPARAM1是我们自定义的规则;
  • NGINXACCESS是我们整体自定义的规则,其中调用了URIPARAM1;

注意:若以上的日志如下

08/Jan/2016:08:27:43 +0800 - 10.10.6.212:8088 10.10.6.110:80 GET /vvv/test/stat/index proptype=11&level=2&srtype=2&city=dz&region=XJ&begindate=2016-01-08&enddate=2016-01-08&apiKey=c2c959b203d669a9a21861379cb4523c&test=2 - 10.10.6.10 HTTP/1.1 [Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36] [JSESSIONID=kq3v6xi2b74j1a9yxvfvcq131] www.test.cn www.test.com 200 0 0 485 209 0"-" 1.642 1.642

其中referrer字段为"www.test.cn",以上匹配可能出现问题。因为我们这个字段的匹配规则为(?:%{URI:referrer}|-),其中"URI"规则为%{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?,因此我们需要在/usr/local/logstash/patterns/nginx中添加REFER如下:

URIPARAM1 [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
REFER (?:%{HOSTNAME}|%{URI})

NGINXACCESS %{HTTPDATE:timestamp} - (?:%{HOSTPORT:upstream}|-) %{HOSTPORT:request_server} %{WORD:request_method} %{URIPATH:uri} %{URIPARAM1:args} - %{IP:clientip} HTTP/%{NUMBER:httpversion} \[%{GREEDYDATA:agent}\] \[%{GREEDYDATA:cookie}\] (?:%{REFER:referrer}|-) %{HOSTNAME:domain} %{NUMBER:status:int} 0 0 %{NUMBER:body_sent:int} %{NUMBER:request_length:int} 0\"(?:%{WORD:cache_status}|-)\"

经过以上调整,正则就可以正确匹配了。

「2.配置文件logstash.conf」

input {
file {
path => "/data/nginx/logs/api.test.log"
type => "nginx-access"
start_position => "beginning"
sincedb_path => "/usr/local/logstash/sincedb"
}
}
filter {
if [type] == "nginx-access" {
grok {
patterns_dir => "/usr/local/logstash/patterns"
match => {
"message" => "%{NGINXACCESS}"
}
}
date {
match => [ "timestamp" , "dd/MMM/YYYY:HH:mm:ss Z" ]
}
}
}
output {
if [type] == "nginx-access" {
elasticsearch {
hosts => ["10.10.10.16:9200"]
manage_template => true
index => "logstash-nginx-access-%{+YYYY-MM}"
}
}

}

其中:patterns_dir定义的是我们自定义的正则存放目录

「3.重启logstash」

查看kibana,字段就显示出来了。如果我们的字段前面是?,可以通过刷新下我们的“Setting”—“indices”—“Index Patterns”—“logstash-nginx-access-%{+YYYY-MM}”索引即可。

总结

经过以上对Grok正则的一番操作,以后即使我们面临更复杂的日志格式,只要对应规则从容不迫,相信我们都可以解决。但是希望大家指导,越复杂的正则匹配只会浪费服务器性能。因此当我们有海量日志的需求时,建议还是使用较容易解析的格式吧。

Image

1.运维思索系列
2.运维管理系列
3.运维监控之路
4.蓝鲸之路
5.CI/CD之路
6.Ansible之路

札记:张牙舞爪的人,往往是脆弱的,因为真正强大的人,是自信的,自信就会温和,温和就会坚定。。

                                                                  --《明朝那些事》

喜欢这篇文章,记得点赞+在看哦~