如何使用混合分隔符(即方括号、space 和双引号)解析日志 ( nginx/apache access.log )?并可选择转换为 json

How to parse logs ( nginx/apache access.log ) with mix of delimiters i.e. square bracket, space and double quotes? and optionally convert to json

nginx access.log。它由 1) 白色 space 2) [ ] 和 3) 双引号分隔。

::1 - - [12/Oct/2021:15:26:25 +0530] "GET / HTTP/1.1" 200 1717 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
::1 - - [12/Oct/2021:15:26:25 +0530] "GET /css/custom.css HTTP/1.1" 200 202664 "https://localhost/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"

解析后应该看起来像

= ::1

= [12/Oct/2021:15:26:25 +0530] or 12/Oct/2021:15:26:25 +0530

= "GET / HTTP/1.1"

= 200

= 1717

= "-"

= "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"

我尝试了一些选项,例如 awk -F'[],] *' awk -f [][{}] ,但它们不适用于整行。

nginx access.log 这里分享的只是一个例子。我正在尝试了解如何混合使用此类定界符来解析其他复杂日志中的用法。

我正在尝试了解如何混合使用此类分隔符来解析其他复杂日志中的用法。

仅在 GNU AWK 中设置字段分隔符对于这种情况是不够的,请查看

::1 - - [12/Oct/2021:15:26:25 +0530] "GET / HTTP/1.1" 200 1717 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
::1 - - [12/Oct/2021:15:26:25 +0530] "GET /css/custom.css HTTP/1.1" 200 202664 "https://localhost/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
  • [] 内的空格不是分隔符
  • " 内的空格不是分隔符
  • 所有其他空格都是分隔符

到目前为止,我知道不可能制作出适合字段分隔符的模式,它可以正确检测 个分隔符空格。

基本管道 awk,3 个步骤

  1. 已用 -F'[][]
  2. 解析方括号
  3. -F'\"'
  4. 解析双引号
  5. 打印结果

解析access.log到json

awk -F'[][]' '{ print "remote_addr " "local_time "  }' access.log | awk -F'\"' '{ print    " method-&-path " "  respStatus-&-byteSent "  " http_referer "  " http_agent "  } ' | awk  '{print " { \"remote_addr\" : \"""\" , \"local_time\" : \"" "\" , \"method\" : \"""\" , \"path\" : \"""\" , \"resp_status\" : \"""\" , \"bytes_sent\" : \"""\" , \"http_referer\" : \"""\" , \"http_agent\" : \"""   "" "" "" "" "" "" "" "" "" "" ""\"}"}'

输出

{ "remote_addr" : "::1" , "local_time" : "12/Oct/2021:15:26:25" , "method" : "GET" , "path" : "/css/custom.css" , "resp_status" : "200" , "bytes_sent" : "202664" , "http_referer" : "https://localhost/" , "http_agent" : "Mozilla/5.0   (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36  "}

解析access.log,同时显示字段和值

awk -F'[][]' '{ print "remote_addr " "local_time "  }' access.log | awk -F'\"' '{ print    " method-&-path " "  respStatus-&-byteSent "  " http_referer "  " http_agent "  } ' | awk -F' ' '{print "remote_addr"", local_time " ", method "", path "", resp_status "", bytes_sent "", http_referer "", http_agent ""  "" "" "" "" "" "" "" "" "" "" " }'

输出

remote_addr::1, local_time 12/Oct/2021:15:26:25, method GET, path /, resp_status 200, bytes_sent 1717, http_referer -, http_agent Mozilla/5.0  (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36  

GNU awk

gawk '
    match([=10=], /([^[:blank:]]+) ([^[:blank:]]+) ([^[:blank:]]+) \[([^]]+)\] "([^"]+)" ([[:digit:]]+) ([[:digit:]]+) "([^"]+)" "([^"]+)"/, m) {
        for (i=1; i<=9; i++) print i, m[i]
    }
' file

或 perl 以获得更简洁的正则表达式

perl -nsE '
    if (/(\S+) (\S+) (\S+) \[(.+?)\] "(.+?)" (\d+) (\d+) "(.+?)" "(.+?)"/) {
        for $i (1..9) { say $i, $$i }
    }
' -- -,=" " file

或者使用命名捕获,这将使它更易于使用(但我正在重新发明@Shawn 提到的模块):

perl -MData::Dump=dd -nE '
    dd \%+ if (/
        (?<host>\S+) \s
        (?<ident>\S+) \s
        (?<user>\S+) \s
        \[(?<timestamp>.+?)\] \s
        "(?<request>.+?)" \s
        (?<status>\d+) \s
        (?<size>\d+) \s
        "(?<referer>.+?)" \s
        "(?<user_agent>.+?)"
    /x)
' file

如果你可以使用gnu-awk你可以使用FPAT来指定列数据:

awk -v FPAT='\[[^][]*]|"[^"]*"|\S+' '{
  for(i=1; i<=NF; i++) {
    print "$"i" = ", $i
  }
}' file

模式匹配:

  • \[[^][]*] 使用 negated character class
  • 从开始 [ 到结束 ] 匹配
  • |
  • "[^"]*" 匹配从开始到结束的双引号
  • |
  • \S+ 1 个或多个非空白字符

输出

 =  ::1
 =  -
 =  -
 =  [12/Oct/2021:15:26:25 +0530]
 =  "GET / HTTP/1.1"
 =  200
 =  1717
 =  "-"
 =  "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"

这可能适合您 (GNU sed):

sed -E 'y/ /\n/
       :a;s/^(\[[^]\n]*)\n/ /m;s/^("[^"\n]*)\n/ /m;ta
       s/.*/echo '\''&'\'' | cat -n/e
       s/^  *(\S)\t/$ = /mg' file

用换行符替换所有空格。

将所有以 [] 或双引号开头和结尾的行分组,并用空格替换换行符。

给所有行编号。

删除前导空格和制表符并格式化结果。

因为这些是 nginx 日志,所以它们的格式将相同(或者有一些设置可以使日志保持相同,谈论当前版本)。我们可以利用这个特性,而且我们可以专注于只获取需要的部分,所以我在这里使用正则表达式来只获取匹配的值,并让不需要的值保持简单。通过遵循这一点,我们不需要对字段编号进行硬编码,使用正则表达式就可以解决这个问题。

这应该适用于任何 awk 版本。

awk '
{
  while(match([=10=],/^::[0-9]+|\[?[0-9]{1,2}\/[a-zA-Z]{3}\/[0-9]{4}(:[0-9]{2}){3}\s+\+[0-9]{4}\]?|"[^"]*"|\s[0-9]{3}\s|[0-9]+\s/)){
    val=substr([=10=],RSTART,RLENGTH)
    gsub(/^[[:space:]]+|[[:space:]]+$/,"",val)
    print val
    [=10=]=substr([=10=],RSTART+RLENGTH)
  }
}'  Input_file