配置ELK系統(ElasticSearch+Logstash+Kibana)收集nginx系統日誌(三): Logstash的Grok過濾器外掛原理
Grok是Logstash最重要的外掛, 可以將非結構化日誌資料解析為結構化和可查詢的內容.
此工具非常適用於syslog日誌, apache和其他Web伺服器日誌, mysql日誌, 以及通常為人類而非計算機使用而編寫的任何日誌格式.
grok的語法
%{SYNTAX:SEMANTIC}
SYNTAX是文字要匹配的”patterns”(翻譯為”模式”, 但我覺得翻譯成”型別”更恰當)
SEMANTIC是匹配到的文標識(欄位), 預設是以字串的方式儲存
翻譯成中文就是
%{模式:欄位}
假設有以下日誌片斷
55.3.244.1 GET /index.html 15824 0.043
通過分析, 第一段可能是一個IP, 第二段可能是一個HTTP的請求方法, 第三段可能是請求的頁面, 等等, 所以grok的過濾表示式可以寫為
%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}
經過grok過濾器處理以後, 該日誌片斷將被解析成以下欄位
client: 55.3.244.1 method: GET request: /index.html bytes: 15824 duration: 0.043
知道了怎麼解析日誌, 所以Logstash可以寫成如下配置
input { file { path => "/var/log/http.log" } } filter { grok { match => { "message" => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}" } } } output { #something here. }
Logstash預設提供約120種常見文字”模式”(ofollow,noindex" target="_blank">參考這裡 ), 例如: NUMBER, IP, USERNAME, EMAILADDRESS, INT, UUID, MAC, PATH, TIMESTAMP, DATE, WORD, HOSTNAME, 等
我一直好奇,為什麼要把SYNTAX翻譯成”模式”, 而不翻譯成”型別”(因為一些常見的”模式”都是一種”型別”啊), 那是因為, “模式”並不僅僅是一種”型別”, 更有可能是多個”型別”的組合 , 例如以下都是”模式”的定義
USERNAME [a-zA-Z0-9._-]+ USER %{USERNAME} EMAILLOCALPART [a-zA-Z][a-zA-Z0-9_.+-=:]+ EMAILADDRESS %{EMAILLOCALPART}@%{HOSTNAME} INT (?:[+-]?(?:[0-9]+)) NUMBER (?:%{BASE10NUM}) IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)? IPV4 (?<![0-9])(?:(?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5])[.](?:[0-1]?[0-9]{1,2}|2[0-4][0-9]|25[0-5]))(?![0-9]) IP (?:%{IPV6}|%{IPV4}) HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b) IPORHOST (?:%{IP}|%{HOSTNAME}) HOSTPORT %{IPORHOST}:%{POSINT} PATH (?:%{UNIXPATH}|%{WINPATH}) UNIXPATH (/([\w_%!$@:.,+~-]+|\\.)*)+ TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+)) WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+ URIPROTO [A-Za-z]([A-Za-z0-9+\-.]+)+ URIHOST %{IPORHOST}(?::%{POSINT:port})? URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%&_\-]*)+ URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]<>]* URIPATHPARAM %{URIPATH}(?:%{URIPARAM})? URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})? # Shortcuts QS %{QUOTEDSTRING} SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])? SYSLOGHOST %{IPORHOST} SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}> # Log formats SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}: # HTTPD Log formats HTTPD_COMMONLOG %{IPORHOST:clientip} %{HTTPDUSER:ident} %{HTTPDUSER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-) HTTPD_COMBINEDLOG %{HTTPD_COMMONLOG} %{QS:referrer} %{QS:agent} # HTTPD Error logs HTTPD20_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{LOGLEVEL:loglevel}\] (?:\[client %{IPORHOST:clientip}\] ){0,1}%{GREEDYDATA:message} HTTPD24_ERRORLOG \[%{HTTPDERROR_DATE:timestamp}\] \[%{WORD:module}:%{LOGLEVEL:loglevel}\] \[pid %{POSINT:pid}(:tid %{NUMBER:tid})?\]( \(%{POSINT:proxy_errorcode}\)%{DATA:proxy_message}:)?( \[client %{IPORHOST:clientip}:%{POSINT:clientport}\])?( %{DATA:errorcode}:)? %{GREEDYDATA:message} HTTPD_ERRORLOG %{HTTPD20_ERRORLOG}|%{HTTPD24_ERRORLOG}
示例: Grok新增自定義模式
這裡我們自定義一個10-11位文字的模式”POSTFIX_QUEUEID”
$ cat ./patterns/postfix POSTFIX_QUEUEID [0-9A-F]{10,11}
接下來我們來解析以下日誌
Jan 1 06:25:43 mailserver14 postfix/cleanup[21403]: BEF25A72965: message-id=<[email protected]>
Grok的寫法為
filter { grok { patterns_dir => ["./patterns"] match => { "message" => "%{SYSLOGBASE} %{POSTFIX_QUEUEID:queue_id}: %{GREEDYDATA:syslog_message}" } } }
經過grok過濾器處理以後, 該日誌片斷將被解析成以下欄位
timestamp: Jan 1 06:25:43 logsource: mailserver14 program: postfix/cleanup pid: 21403 queue_id: BEF25A72965 syslog_message: message-id=<[email protected]>
請注意: SYSLOGBASE是一個包括了多種”模式”的模式, 因此裡面的欄位是固定的, 例如logsource, program, pid
示例: Grok自帶方法
Grok過濾器自帶了一些配置選項, 下面隨機演示一個
filter { grok { match => { "message" => "%{IPORHOST:remote_ip} - %{DATA:user_name} \[%{HTTPDATE:access_time}\] \"%{WORD:http_method} %{DATA:url} HTTP/%{NUMBER:http_version}\" %{NUMBER:response_code} %{NUMBER:body_sent_bytes} \"%{DATA:referrer}\" \"%{DATA:agent}\"" } remove_field => "message" } }
remove_field這個方法, 顧名思義, 是將整個日誌條目移除. 為什麼要移除? 因為 在上面已經通過match方法獲得了remote_ip/access_time/http_method/http_version/response_code等欄位資訊, 整條日誌已經沒有意義了, 所以這裡可以remove之.
其它的一些Grok過濾器自帶的配置選項, 可以參考官網的這篇文章 .
其它有用內容
1,Grok Debugger 這個網站, 除了可以幫你測試寫出的模式是否可以正確解析日誌以外, 還提供了一些Grok其它的”模式”. 例如, Grok自帶的”模式”裡, 有Apache的”模式”, 卻沒有nginx的”模式”, 其實nginx的”模式”可以在這裡 找到.