在生产环境中,nginx日志格式往往使用的是自定义的格式,我们需要把logstash中的message结构化后再存储,方便kibana的搜索和统计,因此需要对message进行解析。
本文采用grok过滤器,使用match正则表达式解析,根据自己的log_format定制。
1、nginx日志格式
log_format配置如下:
log_format main '$remote_addr - $remote_user [$time_local] $http_host $request_method "$uri" "$query_string" ' '$status $body_bytes_sent "$http_referer" $upstream_status $upstream_addr $request_time $upstream_response_time ' '"$http_user_agent" "$http_cdn_src_ip" "$http_x_forwarded_for"' ;
对应的日志如下:
1.1.1.1 - - [06/Jun/2016:00:00:01 +0800] www.test.com GET "/api/index" "?cms=0&rnd=1692442321" 200 4 "http://www.test.com/?cp=sfwefsc" 200 192.168.0.122:80 0.004 0.004 "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36" "2.2.2.2" "-"
2、编写正则表达式
logstash中默认存在一部分正则让我们来使用,可以访问Grok Debugger来查看,可以在 $logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-4.0.0/patterns/ 目录里面查看。
基本定义在grok-patterns中,我们可以使用其中的正则,当然并不是所有的都适合nginx字段,这时就需要我们自定义正则,然后通过指定patterns_dir来调用。
同时在写正则的时候可以使用Grok Debugger或者Grok Comstructor工具来帮助我们更快的调试。在不知道如何使用logstash中的正则的时候也可使用Grok Debugger的Descover来自动匹配。
1)nginx标准日志格式
logstash自带的grok正则中有Apache的标准日志格式:
COMMONAPACHELOG %{IPORHOST:clientip} %{HTTPDUSER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}
对于nginx标准日志格式,可以发现只是最后多了一个 $http_x_forwarded_for 变量。则nginx标准日志的grok正则定义为:
MAINNGINXLOG %{COMBINEDAPACHELOG} %{QS:x_forwarded_for}
2)自定义格式
通过log_format来匹配对应的正则如下:
%{IPORHOST:remote_addr} - (%{USERNAME:user}|-) \[%{HTTPDATE:log_timestamp}\] %{HOSTNAME:http_host} %{WORD:request_method} \"%{URIPATH1:uri}\" \"%{URIPARM1:param}\" %{BASE10NUM:http_status} (?:%{BASE10NUM:body_bytes_sent}|-) \"(?:%{URI1:http_referrer}|-)\" (%{BASE10NUM:upstream_status}|-) (?:%{HOSTPORT:upstream_addr}|-) (%{BASE16FLOAT:upstream_response_time}|-) (%{BASE16FLOAT:request_time}|-) (?:%{QUOTEDSTRING:user_agent}|-) \"(%{IPV4:client_ip}|-)\" \"(%{WORD:x_forword_for}|-)\"
这里面有几个是我自定义的正则:
URIPARM1 [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*URIPATH1 (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%&_\- ]*)+URI1 (%{URIPROTO}://)?(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?
HOSTPORT %{IPORHOST}:%{POSINT}
message是每段读进来的日志,IPORHOST、USERNAME、HTTPDATE等都是patterns/grok-patterns中定义好的正则格式名称,对照日志进行编写。
grok pattren的语法为:%{SYNTAX:semantic},":" 前面是grok-pattrens中定义的变量,后面可以自定义变量的名称。(?:%{SYNTAX:semantic}|-)这种形式是条件判断。
如果有双引号""或者中括号[],需要加 \ 进行转义。
详解自定义正则:
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]<>]*
URIPARM1 [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]* grok-patterns中正则表达式,可以看到grok-patterns中是以“?”开始的参数,在nginx的 $query_string 中已经把“?”去掉了,所以我们这里不再需要“?”。
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%&_\-]*)+
URIPATH1 (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%&_\- ]*)+ grok-patterns中正则表达式,grok-patterns中的URIPATH不能匹配带空格的URI,于是在中间加一个空格。
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?
URI1 (%{URIPROTO}://)?(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?grok-patterns中正则表达式,grok-patterns的URI不能匹配不带 URIPROTO 的referrer,将 URIPROTO 设置为可有可无。
HOSTPORT %{IPORHOST}:%{POSINT} Grok Debugger上的 HOSTPORT 变量与elastic官方的变量不同,这里重新定义一次,在配置文件中可以不添加该项。
nginx左右字段都定义完成,可以使用Grok Debugger或者Grok Comstructor工具来测试。添加自定义正则的时候,在Grok Debugger中可以勾选“Add custom patterns”。
以上日志匹配结果为:
{ "remote_addr": [ "1.1.1.1" ], "user": [ "-" ], "log_timestamp": [ "06/Jun/2016:00:00:01 +0800" ], "http_host": [ "www.test.com" ], "request_method": [ "GET" ], "uri": [ "/api/index" ], "param": [ "?cms=0&rnd=1692442321" ], "http_status": [ "200" ], "body_bytes_sent": [ "4" ], "http_referrer": [ "http://www.test.com/?cp=sfwefsc" ], "port": [ null ], "upstream_status": [ "200" ], "upstream_addr": [ "192.168.0.122:80" ], "upstream_response_time": [ "0.004" ], "request_time": [ "0.004" ], "user_agent": [ ""Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"" ], "client_ip": [ "2.2.2.2" ], "x_forword_for": [ null ]}
3、logstash的配置文件
创建自定义正则目录
# mkdir -p /usr/local/logstash/patterns# vi /usr/local/logstash/patterns/nginx
然后写入上面自定义的正则
URIPARM1 [A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*URIPATH1 (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%&_\- ]*)+URI1 (%{URIPROTO}://)?(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?NGINXACCESS %{IPORHOST:remote_addr} - (%{USERNAME:user}|-) \[%{HTTPDATE:log_timestamp}\] %{HOSTNAME:http_host} %{WORD:request_method} \"%{URIPATH1:uri}\" \"%{URIPARM1:param}\" %{BASE10NUM:http_status} (?:%{BASE10NUM:body_bytes_sent}|-) \"(?:%{URI1:http_referrer}|-)\" (%{BASE10NUM:upstream_status}|-) (?:%{HOSTPORT:upstream_addr}|-) (%{BASE16FLOAT:upstream_response_time}|-) (%{BASE16FLOAT:request_time}|-) (?:%{QUOTEDSTRING:user_agent}|-) \"(%{IPV4:client_ip}|-)\" \"(%{WORD:x_forword_for}|-)\"
logstash.conf配置文件内容
input { file { path => "/data/nginx/logs/access.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 => [ "log_timestamp" , "dd/MMM/YYYY:HH:mm:ss Z" ] } urldecode { all_fields => true } //把所有字段进行urldecode(显示中文) mutate { gsub => ["user_agent","[\"]",""] //将user_agent中的 " 换成空 } }}output { if [type] == "nginx-access" { elasticsearch { hosts => ["10.10.10.26:9200"] manage_template => true index => "logstash-nginx-access-%{+YYYY-MM}" } }}
4、启动logstash,然后就可以查看日志是否写入elasticsearch中。
联系客服