本部分实践案例,旨在通过一种场景多种解决方案的对比,选择出一种最快最好的解决方案。本专题主要讲解正则解析方面的场景实践。
场景:解析Nginx日志
以下以一条Nginx日志为例,向大家展开如何解析Nginx日志的多种方案。
203.208.60.89 - - [04/Jan/2019:16:06:38 +0800] "GET /atom.xml HTTP/1.1" 200 273932 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
需求
1、从Nginx日志中提取出clientip、ident、auth、timestamp、verb、request、url、httpversion、response、bytes、referrer、agent信息
2、对解析出来的url进行再提取,提取出url_proto、url_host、url_param
3、对解析出来的url_param进行再提取,提取出url_path、url_query信息
原始日志
在控制台收集到的日志格式是string格式,如下所示:
__source__: 30.43.16.15
__tag__:__client_ip__: 12.120.75.140
__tag__:__receive_time__: 1563443076
content: 203.208.60.89 - - [04/Jan/2019:16:06:38 +0800] "GET http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0 HTTP/1.1" 200 273932 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
LOG DSL编排
本部分将提供两种方案,解决以上需求。
方案一:正则解析
1、针对需求1解析Nginx日志的加工编排如下:
e_regex("content",r'(?P<ip>\d+\.\d+\.\d+\.\d+)( - - \[)(?P<datetime>[\s\S]+)\] \"(?P<verb>[A-Z]+) (?P<request>[\S]*) (?P<protocol>[\S]+)["](?P<code>\d+) (?P<sendbytes>\d+) ["](?P<refere>[\S]*)["] ["](?P<useragent>[\S\s]+)["]')
预览处理日志:
ip: 203.208.60.89
datetime: 04/Jan/2019:16:06:38 +0800
verb: GET
request: http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
protocol: HTTP/1.1
code: 200
sendbytes: 273932
refere: -
useragent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
2、针对需求2解析第一步加工后得到的url的加工编排如下:
e_regex('url',r'(?P<url_proto>(\w+)):\/\/(?P<url_domain>[a-z0-9.]*[^\/])(?P<uri_param>(.+)$)')
预览处理日志:
url_proto: http
url_domain: cdn1cdedge0001.coxlab.net
uri_param: /_astats?application=&inf.name=eth0
3、针对需求3解析第二步得到的url参数的加工编排如下:
e_regex('uri_param',r'(?P<uri_path>\/\_[a-z]+[^?])\?(?<uri_query>(.+)$)')
预览处理日志:
uri_path: /_astats
uri_query: application=&inf.name=eth0
4、综上LOG DSL规则可以如以下形式:
"""第一步:初步解析Nginx日志"""
e_regex("content",r'(?P<ip>\d+\.\d+\.\d+\.\d+)( - - \[)(?P<datetime>[\s\S]+)\] \"(?P<verb>[A-Z]+) (?P<request>[\S]*) (?P<protocol>[\S]+)["](?P<code>\d+) (?P<sendbytes>\d+) ["](?P<refere>[\S]*)["] ["](?P<useragent>[\S\s]+)["]')
"""第二步:解析第一步得到的url"""
e_regex('url',r'(?P<url_proto>(\w+)):\/\/(?P<url_domain>[a-z0-9.]*[^\/])(?P<uri_param>(.+)$)')
"""第三步:解析第二步的到的url参数"""
e_regex('uri_param',r'(?P<uri_path>\/\_[a-z]+[^?])\?(?<uri_query>(.+)$)')
预览综上处理后的日志如下:
__source__: 30.43.16.15
__tag__:__client_ip__: 12.120.75.140
__tag__:__receive_time__: 1563443076
content: 203.208.60.89 - - [04/Jan/2019:16:06:38 +0800] "GET http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0 HTTP/1.1" 200 273932 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
ip: 203.208.60.89
datetime: 04/Jan/2019:16:06:38 +0800
verb: GET
request: http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
protocol: HTTP/1.1
code: 200
sendbytes: 273932
refere: -
useragent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
url_proto: http
url_domain: cdn1cdedge0001.coxlab.net
uri_param: /_astats?application=&inf.name=eth0
uri_path: /_astats
uri_query: application=&inf.name=eth0
方案二:Grok解析
1、使用grok模式解析Nginx日志,只需要COMBINEDAPACHELOG模式即可。
模式 | 规则 | 说明 | ||
---|---|---|---|---|
COMMONAPACHELOG | `%{IPORHOST:clientip} %{HTTPDUSER:ident} %{USER:auth} [%{HTTPDATE:timestamp}] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})? | %{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes} | -)` | 解析出clientip、ident、auth、timestamp、verb、request、httpversion、response、bytes字段内容 |
COMBINEDAPACHELOG | %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent} |
解析出上一行中所有字段,另外还解析出referrer、agent字段 |
针对需求1解析Nginx日志的加工编排如下:
e_regex('content',grok('%{COMBINEDAPACHELOG}'))
预览处理日志:
clientip: 203.208.60.89
ident: -
auth: -
timestamp: 04/Jan/2019:16:06:38 +0800
verb: GET
request: http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
httpversion: 1.1
response: 200
bytes: 273932
referrer: "-"
agent: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
2、解析request只需要使用grok的以下几种模式组合即可完成解析:
模式 | 规则 | 说明 |
---|---|---|
URIPROTO | [A-Za-z]+(\+[A-Za-z+]+)? |
匹配url中的头部分,如http://hostname.domain.tld/_astats?application=&inf.name=eth0 会匹配到http |
USER | [a-zA-Z0-9._-]+ |
匹配字母、数字和._- 组合 |
URIHOST | %{IPORHOST}(?::%{POSINT:port})? |
匹配IPORHOST和POSINT |
URIPATHPARAM | %{URIPATH}(?:%{URIPARAM})? |
匹配url参数部分 |
针对需求2解析第一步加工后得到的request的加工编排如下:
e_regex('request',grok("%{URIPROTO:uri_proto}://(?:%{USER:user}(?::[^@]*)?@)?(?:%{URIHOST:uri_domain})?(?:%{URIPATHPARAM:uri_param})?"))
预览处理日志:
url_proto: http
url_domain: cdn1cdedge0001.coxlab.net
uri_param: /_astats?application=&inf.name=eth0
3、解析url_param可以使用grok的以下模式即可完成解析:
模式 | 规则 | 说明 |
---|---|---|
GREEDYDATA | .* |
匹配任意或多个除换行符 |
针对需求3解析第二步得到的url参数的加工编排如下:
e_regex('url_param',grok("%{GREEDYDATA:uri_path}\?%{GREEDYDATA:uri_query}"))
预览处理日志:
uri_path: /_astats
uri_query: application=&inf.name=eth0
4、综上LOG DSL规则可以如以下形式:
"""第一步:初步解析Nginx日志"""
e_regex('content',grok('%{COMBINEDAPACHELOG}'))
"""第二步:解析第一步得到的url"""
e_regex('request',grok("%{URIPROTO:uri_proto}://(?:%{USER:user}(?::[^@]*)?@)?(?:%{URIHOST:uri_domain})?(?:%{URIPATHPARAM:uri_param})?"))
"""第三步:解析第二步的到的url参数"""
e_regex('url_param',grok("%{GREEDYDATA:uri_path}\?%{GREEDYDATA:uri_query}"))
预览综上处理后的日志如下:
__source__: 30.43.16.15
__tag__:__client_ip__: 12.120.75.140
__tag__:__receive_time__: 1563443076
content: 203.208.60.89 - - [04/Jan/2019:16:06:38 +0800] "GET http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0 HTTP/1.1" 200 273932 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
clientip: 203.208.60.89
ident: -
auth: -
timestamp: 04/Jan/2019:16:06:38 +0800
verb: GET
request: http://cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
httpversion: 1.1
response: 200
bytes: 273932
referrer: "-"
agent: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
url_proto: http
url_domain: cdn1cdedge0001.coxlab.net
uri_param: /_astats?application=&inf.name=eth0
uri_path: /_astats
uri_query: application=&inf.name=eth0
对比
综上所述,可以看出使用正则解析和Grok模式解析Nginx日志两种方案优劣。
正则方案
对于不是很熟悉的开发人员使用正则解析日志效率会比较低,而且学习成本会比较大,另外一点是灵活性不够,比如在request内容改成
http://twiss@cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
那么还使用以上正则
(?P<url_proto>(\w+)):\/\/(?P<url_domain>[a-z0-9.]*[^\/])(?P<uri_param>(.+)$)
request则会解析成
url_proto: http
url_domain: twiss@
uri_param: cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
很显然,如果还使用原来的正则模式的话,解析出来的内容是不符合要求的。因此,还需要修改正则模式才能正常解析。由此可见,灵活的使用正则的解析的难度比较高。
Grok方案
Grok模式解析对于开发人员是友好的,对于非开发人员亦然如此。Grok学习成本低,只需要了解哪些模式代表的哪些字段类型就可以轻松解析你想解析的日志内容。Grok学习曲线低,可以通过用户文档中GROK参考来学习实践。
Grok灵活性高,比如还是以上述正则方案中例子为参考:
request内容改成
http://twiss@cdn1cdedge0001.coxlab.net/_astats?application=&inf.name=eth0
Grok模式不变
e_regex('request',grok("%{URIPROTO:uri_proto}://(?:%{USER:user}(?::[^@]*)?@)?(?:%{URIHOST:uri_domain})?(?:%{URIPATHPARAM:uri_param})?"))
request则会解析成
url_proto: http
user: twiss
url_domain: cdn1cdedge0001.coxlab.net
uri_param: /_astats?application=&inf.name=eth0
在Grok模式不变的情况下,request添加user的情况下,还是能够正确解析出正确的日志内容。
结论
从灵活性、高效性、低成本、学习曲线等方面对比, GROK都要比直接使用正则表达式要有优势. 但是GROK模式的本质其实还是正则表达式, 但是数据加工已经提供了400种模式包装了场景的正则, 建议优先使用. 当然在需要的情况下, 也可以混合使用GROK与正则甚至自行编写需要的正则.
进一步参考
欢迎扫码加入官方钉钉群获得实时更新与阿里云工程师的及时直接的支持: