尽量少使用grok进行过滤可以减轻logstash的负担,提高处理效率。
首先将Nginx日志格式作调整
log_format json '{"@timestamp":"$time_iso8601",' '"@source":"$server_addr",' '"@nginx_fields":{' '"client":"$remote_addr",' '"size":$body_bytes_sent,' '"responsetime":"$request_time",' '"upstreamtime":"$upstream_response_time",' '"upstreamaddr":"$upstream_addr",' '"request_method": "$request_method",' '"domain":"$host",' '"url":"$uri",' '"http_user_agent": "$http_user_agent",' '"status":$status,' '"x_forwarded_for":"$http_x_forwarded_for"}}'; access_log /data/app_data/nginx/logs/access.log json;
这里需要特别注意的是,要分析的日志字段类型是字符串还是数字。如果是日志格式是JSON格式的,那么数字类型的字段显示成为 - ,JSON格式将出错。
例如在JSON中如果responsetime格式是字符串,那么"-"可以解析,如果是数字,JSON将不能正确解析。
"responsetime": "-",
"responsetime": -,
如果JSON格式出错,那么Logstash解析JSON就会出错,相应的JSON字段将不会再Kibana显示。
这里要特别注意索引字段的类型,Kibana会默认根据首次接收到的数据创建索引类型。如果后期更好了数据来源的类型,需要更改Elasticsearch的mapping。
例如原来Nginx的 reponsetime设置的是数字类型,但是访问一些错误URL时,reponsetime字段的值将会显示 - ,这时JSON格式将出错,所以决定把Nginx日志JSON格式的所有数字类型都用""括起来。但是由于之前一直是数字类型,所以更改JSON格式后,有些Nginx日志无法在Kibana页面进行搜索。这时就需要更改mapping。
nginx access log相关的logstash配置如下:
file { type => "nginx_access" path => ["/data/app_data/nginx/logs/*.log"] exclude => ["*.gz","error.log"] sincedb_path => "/dev/null" codec => "json" }
解析error log
Nginx错误日志经常有一行信息显示成为多行的情况,例如PHP程序的一些报错会分成多行显示,这样Logstash默认会把一条Nginx错误日志当成多行处理。这种情况下就需要使用logstash的multiline对多行进行合并。
如果不清楚应该怎么样去合并多行,可以使用logstash的 rubydebug输出日志看看logstash是怎么输出的,然后根据相应的规则匹配进行合并。
multiline可以在codec和filter中使用,合并后的日志会比同一时间的其他日志慢点显示。
例如这条错误日志:
2015/04/17 18:16:43 [error] 32320#0: *7664344 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught exception 'Yaf_Exception' with message 'There is no section 'game' in 'xxxxxxxxxxxxxxxxxxxxxxx'' in router.php:13 Stack trace: #0 router.php(13): Yaf_Application->__construct('/data/...', 'xx') #1 {main} Next exception 'Yaf_Exception_StartupError' with message 'Initialization of application config failed' in router.php:13 Stack trace: #0 router.php(0): Yaf_Application::__construct() #1 {main} thrown in router.php on line 13" while reading response header from upstream, client: 180.150.179.184, server: xxxx.com, request: "GET /router.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "xxx.com"
这个是PHP使用Yaf框架产生的错误日志,Nginx分成了多行显示。这里就需要对多行日志进行匹配
匹配 #,换行符,Next,Stack和空白开头的行
file { type => "nginx_error" path => "/data/app_data/nginx/logs/error.log" exclude => ["*.gz"] sincedb_path => "/dev/null" }
filter { if [type] == "nginx_error" { multiline { pattern => "^(Stack trace:|#|Next|\\n|$|\s)" what => "previous" } } }
可以使用logstash的geoip过滤处理Nginx的来源IP然后通过Kibana在页面进行地图展示
if [type] == "nginx_access" { if [@nginx_fields][x_forwarded_for] != "-" { geoip { source => "[@nginx_fields][x_forwarded_for]" target => "geoip" database => "/data/app_platform/logstash/vendor/geoip/GeoLiteCity.dat" add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ] add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ] } mutate { convert => [ "[geoip][coordinates]", "float" ] } } else if [@nginx_fields][client] != "-" { geoip { source => "[@nginx_fields][client]" target => "geoip" database => "/data/app_platform/logstash/vendor/geoip/GeoLiteCity.dat" add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ] add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ] } mutate { convert => [ "[geoip][coordinates]", "float" ] } }
这里有两个字段都可能含有外网IP,client和x_forwarded_for,这里需要注意一下这里的写法,@nginx_fields下面的client需要写成[@nginx_fields][client] 而不能写成@nginx_fields.client
还有GeoIP的IP库可能会与真实的IP数据有出入,可以定期下载最新的IP库更新。
参考文档:
https://blog.pkhamre.com/logging-to-logstash-json-format-in-nginx/
http://xylil.com/2012/07/14/nginx-logs-in-json-format/
https://community.ulyaoth.net/threads/how-to-create-a-logstash-geoip-based-dashboard-in-kibana-3.29/
http://blog.51yip.com/apachenginx/1277.html
http://www.logstash.net/docs/1.4.2/filters/multiline
http://www.505forensics.com/who-have-your-logs-been-talking-to/
https://www.digitalocean.com/community/tutorials/how-to-map-user-location-with-geoip-and-elk-elasticsearch-logstash-and-kibana