Logstash常用插件的使用
Elk作为日志收集分析系统,除了使用filebeat及logstash等工具收集日志外,另一个重要的功能就是分析日志,分析日志是依据日志中的一些关键字段对日志进行切段取值,所以,在分析日志前就需要将这些关键字段取出来。logstash有自己的filter过滤插件,专门用来对日志进行切割,过滤,最终保留日志中自己需要的部分,删除日志中多余的部分,将过滤出来的日志写入到elasticsearch中。
Logstash官方文档中介绍的logstash的过滤插件大概有四十多种,logstash常用的过滤插件有grok、kv、mutate、urldecode、dissect、geoip、data插件等。
一、常用的logstash插件的介绍
1、grok插件的使用
Grok是logstash最主要的过滤插件,grok是通过系统预定义的正则表达式或者通过自己定义正则表达式来匹配日志中的各个值,安装了logstash后默认会有一些常用软件的日志匹配正则存放在(logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-4.1.1/patterns/)目录下,在使用时自己可以根据自己的日志格式或者对匹配正则进行调整或者直接调用。如果,自己想在其他目录定义正则规则匹配日志,在使用时需要指定正则的路径。
(1)Grok过滤插件的基本语法格式
filter{
grok {
patterns_dir => [“./patterns”]
match => {“message”=>“%{SYSLOGBASE}%{POSTFIX_QUEUEID:queue_id}:%{GREEDYDATA:syslog_message}”}
remove_field => [“K1”,”K2”]
}
(2
)常用的配置选项
1)match:
match作用:用来对字段的模式进行匹配
语法:
filter{
grok {match => {“message”=> [“Duration:%{NUMBER:duration}”,“Speed:%{NUMBER:speed}”]}}
}
当需要匹配多条不同的日志时可以写为:
match => [
"message","%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\]",
"message", "(?<datetime>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) \[(?<errtype>\w+)\] (?<other1>\S+:) \*\d+ (?<errmsg>[^,]+), (?<errinfo>.*)$",
"message", "(?<datetime>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) \[(?<errtype>\w+)\] (?<other1>\S+:) (?<errmsg>[^,]+)"
]
2
)
patterns_dir
作用:用来指定规则的匹配路劲,如果使用logstash自定义的规则时,不需要写此参数。Patterns_dir可以同时制定多个存放过滤规则的目录。
语法格式:
patterns_dir => [“/ opt / logstash / patterns”,“/ opt / logstash / extra_patterns”]
3
)
remove_field
作用:如果匹配到某个”日志字段,则将匹配的这个日志字段从这条日志中删除。
语法:
grok {
remove_field => [“foo _%{somefield}”]
}
也可以同时删除多个匹配到的切割后的日志段。
grok {
remove_field => [“foo _%{somefield}”,“my_extraneous_field”]
}
Grok
过滤正则规则可以自己根据自己的日志格式自行编写,在编写
grok
过滤规则时容易出错,此时可以使用
grokdebug
网站对自己的日志及编写的正则规则进行调试,
grokdebug
网址为(
https://grokdebug.herokuapp.com/
),由于是国外网站,访问时经常性的会出问题,我们可以在自己本地搭建调试站点,参考(
https://blog.csdn.net/qq_33588470/article/details/53079293
)。
2、kv插件的使用
Grok切割匹配日志是根据正则表达式来匹配的,kv插件也可对日志进行切割,kv是以指定的特殊字符串对日志以key-value形式的切割,”key”和”value”都存在于日志中。Kv插件中默认字符段的分隔符是“ “,”key”和”value”的分割符是”=”。而Grok插件中中”key”是自定义的,”value”是通过正则匹配出来的。所以对不规律的日志只能使用grok规律,对于有规律的日志使用kv插件更方便。
(1)kv插件的基本语法格式
kv {
source => "billPayment"
field_split => "\,"
value_split => ":"
}
(2)kv插件常用的配置选项
1)allow_duplicate_values
作用:用于删除重复键/值对的bool选项。当设置为false时,只保留一个唯一的键/值对。
用法:
kv {
allow_duplicate_values => false
}
2
)
field_split
作用:指定多对key-value之间的分割符,默认的分割符是” ”。
用法:
kv {
field_split =>“&?”
}
3
)
include_brackets
作用:指定是否将方括号,尖括号和括号视为值“包装器”,应该从值中删除,值为布尔值,true或者fasle
用法:
kv {
include_brackets => true
}
4
)
source
作用:选择过滤字符串的来源
用法:
kv {
source =>“not_the_message”
}
5
)
value_split
作用:指定做分割的日志中“key-value”之间的分割符,默认的值为”=”
用法:
kv {
value_split =>“:”
}
3
、
mutate
插件的使用
Mutate主要用来修改、替换、删除及重命名字段中的某些插件。
常用的配置选项:
(1)gsub
作用:使用正则表达式或者字符串替换日志中的某些字符串。
gsub => [
"message", "[/]", "_" #将message中的”/”替换为“_”
]
(2
)
remove_field
作用:将匹配到的”key-value从日志中删除”,几乎所有的插件中都有此选项。
(3)rename
作用:对某些字段重新命名
rename => { "HOSTORIP" => "client_ip" } #将HOSTORIP重命名为”client_ip”
(4
)
replace
作用:用新值替换匹配到的某个字段的”value”部分,
用法如:
replace => { "pay_money"=>"%{pay3_money}.%{pay4_money}" }
4、urldecode插件的用法
Nginx作为反向代理服务器,部分日志在传输的过程中是通过“urlencoded”编码的,通过logstash收集上来是一大串看不懂的字符串,这时urldecode就派上用场了,他可将字符串还原为原来的格式。
Urldecode常用的配置选项:
(1)all_fields
作用:对所有的值进行url的解码,默认值为false
(2)Charset
作用:设置解码使用的字符集,默认为“utf-8”
5、dissect插件的用法
切割的日志中,基本上所有的字段格式是字符串类型的,如果需要在kibana中对收集的日志的部分字段求和或做其他的计算操作,这时,需要将字符串转换为数字格式,这时就用到了dissect插件。
常用的选项:
convert_datatype :对key所对应的value的类型做转化。
用法:
dissect {
convert_datatype => {
totalAmount => "float"
request_time => "float"
}
5、geoip插件的用法
Geoip插件主要是根据访问者ip定位访问者所在的地理位置信息(包括城市名、经纬度等地理位置信息),这个插件需要GeoLite2数据库支持。获取的客户端的信息通过kibana中的”Geo Coordinates”聚合图形更能直观的展示访问的客户端的地理分布信息。
(1)基本的用法:
geoip {
source => "remote_addr"
target => "geoip"
database => "/etc/logstash/GeoLite2-City.mmdb"
}
(2
)常用的配置选项说明
1)Source
作用:用来指定对分割的日志中的哪个”key”对应的ip地址通过geoip插件处理。
2)target
作用:指定Logstash应该存储geoip数据的字段
3)Fields
作用:设置需要geoip中包含的字段,值类型为数组,
4)database
作用:设置geoip2数据库文件所在的地址。
二、生产中使用logstash过滤nginx日志的案例。
生产中的日志多为post请求所产生的日志,日志中的body部分的内容较多。nginx为多个服务做了反向代理,所以产生的日志格式有好几种,但日志中除了body部分日志不尽相同,其他部分日志格式基本相同。
1、nginx产生中常见的几种日志格式如下:
#日志格式一
20.10.100.02 - - [20/Dec/2010:00:00:00 +0000] "POST /serverqr/map/service HTTP/1.1" {\x22requestData\x22:{\x22version\x22:\x221.0\x22,\x22reqType\x22:\x2201\x22,\x22reqSysId\x22:\x2220101220000200\x22,\x22reqOrderNo\x22:\x2220100020000200\x22,\x22orderTime\x22:\x222010-00-20 00:02:00\x22,\x22orderType\x22:\x2210\x22,\x22merOrgId\x22:\x22000000000000000\x22,\x22Info\x22:[{\x22merCatCode\x22:\x220011\x22,\x22merId\x22:\x22000200000000000\x22,\x22qrName\x22:\x22QrCodeSDKDemo\x22,\x22termId\x22:\x2200000002\x22,\x22subId\x22:\x22\x22,\x22subName\x22:\x22\x22}],\x22qrValidTime\x22:\x220000\x22,\x22limitCount\x22:\x221\x22,\x22sysReserve\x22:\x22\x22,\x22txnAmt\x22:\x221\x22,\x22currencyCode\x22:\x22100\x22,\x22payeeComments\x22:\x22hello\x22,\x22backUrl\x22:\x22http://20.10.100.02/test/demo-notify\x22,\x22addnOpUrl\x22:\x22\x22},\x22signature\x22:\x220BA00002EEEB2A10A0022B20C0E0200B\x22} 200 202 "-" "python-requests/2.10.1" "-"
#日志格式二
100.100.00.220 - - [20/Dec/2010:10:20:00 +0000] "POST /server/order HTTP/1.1" {\x22sign\x22:\x220f00d00a0b0cba02ebbb010b2a020000000000020b0000cc2cdc2ad0c0e0f00ab20bdbce0bb10b00de010f2100000ec0cdf002100fabda00c0010a0000000ed0\x22,\x22signkeyindex\x22:\x221\x22,\x22sdkInfo\x22:\x2201.01.01M\x22,\x22service\x22:\x22pay.wap\x22,\x22charest\x22:\x22utf-0\x22,\x22requestData\x22:\x22{\x22orderid\x22:\x22201012201020001010000020110\x22,\x22orgcode\x22:\x2201000000\x22,\x22merno\x22:\x22000200000000000\x22,\x22transdate\x22:\x2220101220102000\x22,\x22txnAmt\x22:\x22000000000100\x22,\x22orderInfo\x22:\x22%E0%00%BF%E0%B0%B0%E0%AC%BE\x22,\x22return_url\x22:\x22http%0A%2F%2F10.100.10.20%0A0000%2FGHT_D0_Pay_Web%2FYSH_H0Submit.jsp\x22,\x22notify_url\x22:\x22http%0A%2F%2F100.100.00.20 %0A0000%2AGHT_D0_Pay_Web%2Fnotify\x22}\x22,\x22signtype\x22:\x22SHA012\x22,\x22formid\x22:\x22000000000000000\x22} 200 00 "-" "Java/1.0.0_00" "-"
#日志格式三
120.00.00.110 - - [20/Dec/2010:00:00:00 +0000] "GET /acqRedirect?qrCode=https://qr.00010.com/00010000/02221010000100010200001000020000 HTTP/1.1" - 002 0 "-" "Mozilla/0.0 (Linux; Android 0.0; VKY-AL00 Build/HUAWEIVKY-AL00; wv) AppleWebKit/000.00 (KHTML, like Gecko) Version/0.0 Chrome/00.0.2000.00 Mobile MQQBrowser/0.2 TBS/000002 Safari/000.00 MicroMessenger/0.0.10.1100 NetType/WIFI Language/zh_CN" "-"
#日志格式四
101.201.00.00 - - [20/Dec/2010:00:00:02 +0000] "POST /server/notify HTTP/1.1" certId=00000000000&comInfo=e0YwPTAyMDAmRjM0MDAwMDAwJkYyNT0wMCZGMzc0MTIyNzAwMzYyMDQ2JkY2MD0wMzAwMDAwMDAwMDA0MDAxMDAwMDAwMDAwMjUwMDExMDB0¤cyCode=100&orderNo=122000002000&orderTime=20101220000020&payerInfo=e2FjY00vPTYyMjg0ODAwMTg0OTcyOTQxNzUmbmFtZT0osK0pm0XmhxxxxxxxxxxxxxxxxxxxxTAzMDAwMCZjYXJkQXR0cj0wMX0=&reqType=0000000000&settleDate=1220&settleKey=00002000 00000002 0200001220000000&txnAmt=1&version=1.0.0&voucherNum=20101220020120002000&signature=FSbfZ+vRKxgmObwyr0vhNFCfaRix0du1Mbo+ODHklYRK0ECao0hNwSenwV0n0N0uK00AZn1MSm2aSisRd/2OZ0V/Hij/njOd120srry0Y0V+M0M0hwK/K1PSjSQe0d00cLhcp0AovBvVWthegtgrGBbpLnU0dd+FziHxxxxxxxxxxxxxxxxxxxx0ksgZswfzOu0ebHlxg0ZDMGYfO0F00BULp0GMeyCuqxgTUdEB0pAUv12YehEP2kO0pnsoB2HbQXXV0bMnAPRdenLcyIGrY0wfQhn0zOg2pF0dfK00NuugSco/EJaDiksitxzcrp20kkl000a2EEouhlRGWY/bxQ== 200 000 "-" "Mozilla/0.0 (compatible; MSIE 0.01; Windows NT 0.0)" "-"
#日志格式五
2010/00/20 11:20:20 [error] 10001#0: *1200210 "/usr/local/nginx/html/server-orp/index.html" is not found (2: No such file or directory), client: 10.0.110.110, server: localhost, request: "GET /server-orp/ HTTP/1.1", host: "10.1.00.01"
2、使用logstash收集日志filter过滤规则配置如下:
filter {
grok {
#使用多条匹配规则匹配不同的日志
match => [
"message", "%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" (?<access_requerst_body>-) %{NUMBER:http_status_code} %{NUMBER:bytes} \"(?:%{GREEDYDATA:http_referrer}|-)\" \"(%{GREEDYDATA:user_agent}|-)\" (%{URIHOST:request_time}|-) (%{URIHOST:upstream_response_time}|-)",
"message", "%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" ({%{NOTSPACE:test_requerst_body}}) %{NUMBER:http_status_code} %{NUMBER:bytes} \"(?:%{GREEDYDATA:http_referrer}|-)\" \"(%{GREEDYDATA:user_agent}|-)\" (%{URIHOST:request_time}|-) (%{URIHOST:upstream_response_time}|-)",
"message", "%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" (?<test2__requerst_body>\S+ \S+) %{NUMBER:http_status_code} %{NUMBER:bytes} \"(?:%{GREEDYDATA:http_referrer}|-)\" \"(%{GREEDYDATA:user_agent}|-)\" (%{URIHOST:request_time}|-) (%{URIHOST:upstream_response_time}|-)",
"message", "%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" %{NOTSPACE:requerst_body} %{NUMBER:http_status_code} %{NUMBER:bytes} \"(?:%{GREEDYDATA:http_referrer}|-)\" \"(%{GREEDYDATA:user_agent}|-)\" (%{URIHOST:request_time}|-) (%{URIHOST:upstream_response_time}|-)",
"message", "%{URIHOST:http_host} %{IP:server_addr} %{IP:remote_addr} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" ({(?<test3_requerst_body>(\S+ \S+ )+\S+|(\S+ \S+)+)}) %{NUMBER:http_status_code} %{NUMBER:bytes} \"(?:%{GREEDYDATA:http_referrer}|-)\" \"(%{GREEDYDATA:user_agent}|-)\" (%{URIHOST:request_time}|-) (%{URIHOST:upstream_response_time}|-)",
"message", "(?<datetime>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) \[(?<errtype>\w+)\] (?<other1>\S+:) \*\d+ (?<errmsg>[^,]+), (?<errinfo>.*)$",
"message", "(?<datetime>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) \[(?<errtype>\w+)\] (?<other1>\S+:) (?<errmsg>[^,]+)"
]
#日志匹配完成后,将匹配到的“message”删除,所匹配的到的日志就是对”message”日志的切割复制。
remove_field => "message"
}
# 将zabbix及python监控产生的日志删除
if [user_agent] == "Zabbix" {
drop {}
}
if [user_agent] == "python-requests/2.18.1" {
drop {}
}
# 将日志中类似"FR=mK"的字段去掉,将没用的字段删除,字段过多影响性能及占用空间
mutate {
gsub => [
"requerst_body", "[a-zA-Z0-9]{2}=[A-Za-z]{4}&|&[a-zA-Z0-9]{2}=[A-Za-z]{4}", ""
]
}
# 使用kv对过滤出来的nginx日志body部分再次过滤
kv {
source => "requerst_body"
field_split => "&"
remove_field => ["buyerPayAmount","buyerId","source","invoiceAmount","connectSys","sign","mid","settleDate","mchntUuid","tid","couponAmount","goodsTradeNo","targetOrderId","notifyId","subInst","seqId","merOrderId","beat","createTime","billQRCode","billDesc"]
}
# 对部分字符解码
urldecode {
all_fields => true
}
# 将billPayment中的"\",","{}"字符去掉
mutate {
gsub => [
"billPayment", "[\\\"\{\}]", ""
]
}
# 使用kv插件再次对billPayment切割
if [billPayment]{
kv {
source => "billPayment"
field_split => "\,"
value_split => ":"
remove_field => ["merOrderId","billDate","buyerUsername","settleDate","buyerId","couponAmount","billBizType","targetOrderId","billNo","paySeqId","billPayment","buyerPayAmount","invoiceAmount","billFunds","srcReverse","attachedData"]
}
}
# 使用kv插件切割错误日志
if [errinfo] {
kv {
source => "errinfo"
field_split => "\,"
value_split => ":"
}
}
# 过滤不同日志中交易金额以便统计总交易额
grok {
match => [
"totalAmount", "(?<pay3_money>([0-9]{1,}))(?<pay4_money>([0-9]{2})$)"
]
}
# 将交易额中的以分为单位字符串转化为以元为单位
#如果交易金额为一位数则进行下面的过滤规则
if [totalAmount] =~ "^[1-9]$" {
mutate {
replace => { "pay_money" => "0.0%{totalAmount}" }
}
}
#如果交易金额为两位数则进行下面的过滤规则
if [totalAmount]=~ "^[1-9][0-9]$" {
mutate {
replace => { "pay_money" => "0.%{totalAmount }" }
}
}
#如果交易金额为三位数则进行下面的过滤规则
if [totalAmount] =~ "[0-9]{3,}" {
mutate {
replace => { "pay_money" => "%{pay3_money}.%{pay4_money}" }
}
}
}
# 更改部分字段数据格式以方便在kibana中统计
dissect {
convert_datatype => {
totalAmount => "float"
request_time => "float"
pay_money => "float"
upstream_response_time => "float"
http_status_code => "int"
bytes => "int"
total_amount => "float"
}
}
# 统计ip地址来源
if [remote_addr] !~ "^127\.|^192\.168\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[01]\.|^10\." {
geoip {
source => "remote_addr"
target => "geoip"
database => "/etc/logstash/GeoLite2-City.mmdb"
}
}
}