项目上线都已经有一段了时间,新的功能在不断上,bug也随着时间慢慢浮现。令人差异的是,每次出现bug都是用户发现的,虽然开发迭代的版主里,一直都没有测试跟着我们走,但是让用户去发现bug实在有点说不过了。在项目初期我用backlog做了info,error文件切分,但是每次看日志都是要用head | more这些命令去看,而且效率也很慢。没这么多时间天天去看error.log。业界做日志处理都是用elk,自己从零开始琢磨到构建完成,一点点心得分享出来,也当作一次学习笔记。
Logstash介绍
Logstash 是开源的服务器端数据处理管道,能够同时 从多个来源采集数据、转换数据,然后将数据发送到您最喜欢的 “存储库” 中。
图片的上部分可以理解为:logstash各种输入源input ,可以从日志文件追加的形式,就像linux tail -f 命令将日志添加进来,或者开启tcp端口,监听文件传输(我就是用这种方法收集),udp方法等等,这些都是基于插件完成的。中间圆形的就是logstash的核心,也是功能最强大的地方filter,可以将日志进行切分,过滤,类型转换。下面想想都知道是输出output,把处理好日志放到elasticsearch,或者redis,mongodb等等都是可以的。
安装启动Logstash
在下载之前请确认电脑是否安装了java,logstash5.0之后的版本好像都需要1.8以上的支持,没有的话,最好装一个jdk1.8。下载地址 点击logstash选择相应的版本即可,下载完成后解压文件后,使用cmd进入解压的目录/bin
如果看见下面信息 9600端口启动了,说明logstash启动成功,网上有些教程是:logstash -e 'input { stdin { } } output { stdout { codec => rubydebug } }'在我的电脑上没有用,这个命令在高版本没用
springboot logback配合
要使用logback一个插件来将数据转成json,引入maven配置
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>4.10</version>
</dependency>
在springboot的resource创建logback.xml文件
<xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="true">
<contextName>logstash-test</contextName>
<!-- 这个是控制台日志输出格式 方便调试对比--->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %contextName %-5level %logger{50} -%msg%n</pattern>
</encoder>
</appender>
<appender name="stash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>localhost:8888</destination> 这是是logstash服务器地址 端口
<encoder class="net.logstash.logback.encoder.LogstashEncoder" /> 输出的格式,推荐使用这个
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="stash"/>
</root>
启动项目,就会请求localhost:8888,如果有监听,就会自动发送日志。
logstash配置
input {
tcp {
host => "localhost" #这个一定要是logstash本机ip,不然logstash 无法启动,也可以去除
port => 8888
codec => json_lines
mode => "server"
}
}
filter {
grok {
match => {
"message" => "%{URIPATH:request} %{IP:clientip} %{NUMBER:response:int} \"%{WORD:sources}\" (?:%{URI:referrer}|-) \[%{GREEDYDATA:agent}\] \{%{GREEDYDATA:params}\}"
}
}
geoip { #利用geoLite数据库,将ip转成经纬度
source => ["clientip"]
database => "E:/java_software/logstash-5.4.0/config/GeoLite2-City.mmdb" }
mutate { remove_field => ["port","@version","level_value"]
}
}
output {
stdout { codec => rubydebug } #标准输出,在命令行中输出方便调试
elasticsearch { hosts => [ "localhost:9200" ]
index => "pybbs"
document_type => "weblog"
}
}
大家看见这个配置文件都是一头雾水吧!主要重点在于filter grok。Grok是将非结构化日志数据解析为结构化和可查询的好方法。
这个工具非常适合syslog日志,apache和其他web服务器日志,mysql日志,以及通常为人类而不是计算机消耗的任何日志格式。
默认情况下,Logstash附带约120个模式。你可以在这里找到它们:https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns
grok模式的语法是%{SYNTAX:SEMANTIC}
这SYNTAX是与您的文字相匹配的图案的名称。例如,3.44将被匹配的NUMBER模式,55.3.244.1并将被匹配的IP模式。语法是你如何匹配。
这SEMANTIC是你给正在匹配的文本的标识符。例如,3.44可能是一个事件的持续时间,所以你可以简单地调用它duration。此外,一个字符串55.3.244.1可能会识别client发出请求。
对于上面的例子,你的Grok过滤器看起来像这样:
%{NUMBER:response}%{IP:clientIp}
或者,您可以将数据类型转换添加到您的grok模式。默认情况下,所有的语义都保存为字符串。如果您希望转换语义的数据类型,例如将字符串更改为整数,则将其后缀与目标数据类型。例如%{NUMBER:num:int},将num语义从一个字符串转换为一个整数。目前唯一支持的转换是int和float。
例子:用这个语法和语义的思想,我们可以从一个样本日志中抽出有用的字段,就像这个虚构的http请求日志:
55.3.244.1 GET /index.html 15824 0.043
这种模式可能是:
%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes}%{NUMBER:duration}
一个更现实的例子,让我们从文件中读取这些日志:
input {
file { path => "/var/log/http.log" }
}
filter {
grok {
match => { "message" => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}"
}
}
}
在grok过滤器之后,会将行日志转化成json格式
client: 55.3.244.1,
method: GET,
request: /index.html,
bytes: 15824,
duration: 0.043,
在调试的过程中,不要一次性把全部的日志打出来,一次性将grok的表达式写出来,如果有一个写错的话,日志切分失败,错误很难排查出来。建议每一个一个字段地添加测试,比较快上手。对于一些特别长的字符串尽量是一些特殊字符包裹起来,比如使用
[“User-agent”]
这样会让gork表达式比较容易匹配
这个是我自己摸索出来日志格式,对应上面gork message
log.info("{} {} {} \"{}\" {} [{}] {}",request.getRequestURI(),IpUtil.getIpAddr(request),executionTime,value,request.getHeader("Referer"),request.getHeader("User-Agent"),“(请求参数)”);
打印的信息从左往右分别是:uri,客户端ip,请求执行时间,日志来源,Referer,User-Agent,我这样摆放是多次实验成果,得出最好方法。像uri,客户端Ip这种比较容易匹配放在前面,这些信息不容易为空,保证日志切分不会失效,像请求参数这些可能为空的字段,一定要放在最后,即使为空了,也不会影响整条日志切分。注意当Referer,User-Agent为空的情况下,会导致整条日志切分失败,在打印日志之前,最好做一下非空处理。
在调试这个logstash过程,我都是参考logstash gork nginx日志格式来写的。
nginx日志:
08/Jan/2016:08:27:43 +0800 - 10.10.6.212:8088 10.10.6.110:80 GET /vvv/te/stat/indexproptype=11&level=2&srtype=2&city=dzion=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
对应的grok表达式
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}|-)\"
最后copy从别人的帖子分享grok参数含义
USERNAME 或 USER
用户名,由数字、大小写及特殊字符(._-)组成的字符串
比如:1234、Bob、Alex.Wong等
EMAILLOCALPART
电子邮件用户名部分,首位由大小写字母组成,其他位由数字、大小写及特殊字符(_.+-=:)组成的字符串。注意,国内的QQ纯数字邮箱账号是无法匹配的,需要修改正则
比如:stone、Gary_Lu、abc-123等
EMAILADDRESS
电子邮件
比如:stone@abc.com、Gary_Lu@gmail.com、abc-123@163.com等
HTTPDUSER
Apache服务器的用户,可以是EMAILADDRESS或USERNAME
INT
整数,包括0和正负整数
比如:0、-123、43987等
BASE10NUM 或 NUMBER
十进制数字,包括整数和小数
比如:0、18、5.23等
BASE16NUM
十六进制数字,整数
比如:0x0045fa2d、-0x3F8709等
BASE16FLOAT
十六进制数字,整数和小数
WORD
字符串,包括数字和大小写字母
比如:String、3529345、ILoveYou等
NOTSPACE
不带任何空格的字符串
SPACE
空格字符串
QUOTEDSTRING 或 QS
带引号的字符串
比如:"This is an apple"、'What is your name?'等
UUID
标准UUID
比如:550E8400-E29B-11D4-A716-446655440000
MAC
MAC地址,可以是Cisco设备里的MAC地址,也可以是通用或者Windows系统的MAC地址
IP
IP地址,IPv4或IPv6地址
比如:127.0.0.1、FE80:0000:0000:0000:AAAA:0000:00C2:0002等
HOSTNAME
主机名称
IPORHOST
IP或者主机名称
HOSTPORT
主机名(IP)+端口
比如:127.0.0.1:3306、api.stozen.net:8000等
PATH
路径,Unix系统或者Windows系统里的路径格式
比如:/usr/local/nginx/sbin/nginx、c:\windows\system32\clr.exe等
URIPROTO
URI协议
比如:http、ftp等
URIHOST
URI主机
比如:www.stozen.net、10.0.0.1:22等
URIPATH
URI路径
比如://www.stozen.net/abc/、/api.php等
URIPARAM
URI里的GET参数
比如:?a=1&b=2&c=3
URIPATHPARAM
URI路径+GET参数
比如://www.stozen.net/abc/api.php?a=1&b=2&c=3
URI
完整的URI
比如:http://www.stozen.net/abc/api.php?a=1&b=2&c=3
日期时间表达式
MONTH
月份名称
比如:Jan、January等
MONTHNUM
月份数字
比如:03、9、12等
MONTHDAY
日期数字
比如:03、9、31等
DAY
星期几名称
比如:Mon、Monday等
YEAR
年份数字
HOUR
小时数字
MINUTE
分钟数字
SECOND
秒数字
TIME
时间