grep+sed+awk简单实战,日志分析示例

问题场景

测试Burrow的评估功能,加了自定义打印的日志,然后每一条自己打印的日志格式是这样的

{"level":"warn","ts":1532414931.7416606,"msg":"[XYZ]Update Offset: 2931 Lag: 248 Timestamp: 1532414619051",
"type":"module","coordinator":"storage","class":"inmemory","name":"mystorage","worker":0,
"cluster":"mycluster","consumer":"myconsumers","topic":"xyz-test","partition":0,"topic_partition_count":0,
"offset":2931,"timestamp":1532414619051,"owner":"","request":"StorageSetConsumerOffset"}

原本就是一行,为了方便阅读,我手动换行,我感兴趣的是msg对应的3个字段:OffsetLagTimestamp。很自然地,可以用正则表达式去做。由于对其他的信息不感兴趣,所以也不必大费周章用个JSON解析库。
本来想着趁机复习下C++的regex,然并卵,脚本语言才是王道,虽然现在我看文档写C++可能会比搜资料写脚本快,但是在熟悉的前提下,写脚本肯定是快些。Python是非常棒的脚本语言,但是对于这种简单的任务而言,一条命令就能搞定,即使是Python也得打开文件之类,一旦写了代码,也会忍不住去错误处理。最后选择用grep+sed+awk这个文本处理三剑客。

1. grep提取目标行

grep "\[XYZ\].\+Offset" burrow.log

grep是查找文件burrow.log中包含的字符串,相当于regex_search,而非regex_matchgrep采用的是标准正则表达式(Basic Regex Expression, BRE),因此+必须加上转义符号\+
使用扩展正则表达式(Extend Regex Expression, ERE)可以加上-e选项,也可以干脆用egrep

egrep "\[XYZ\].+Offset" logs/burrow.log | less

2. sed分析关键词

这一步等价于正则替换regex_replace,我们实际需要的是这一段

Offset: 2931 Lag: 248 Timestamp: 1532414619051

于是只用简单地解析数字,注意sed只支持标准正则表达式,甚至连\d这种扩展是不识别的,只能用[[:digit:]],之前我一直以为vim的替换是用sed,实际上只是形式相近,两者并没什么关系。
参考链接search-and-replace-inconsistency-between-vim-and-sed
如果把第1步grep的结果> res重定向到文件res中,vim打开该文件后只要切换到命令模式下输入

:%s/.*Offset: \(\d\+\) Lag: \(\d\+\) Timestamp: \(\d\+\).*/\1 \2 \3/g

就可以批量替换文件,但是这种方式不便于把整个过程合成一步,因此还是要用sed

sed -i 's/.*Offset: \([[:digit:]]\+\) Lag: \([[:digit:]]\+\) Timestamp: \([[:digit:]]\+\).*/\1 \2 \3/' res

-i选项是修改文件,不加该选项就是将结果打印到标准输出。sed可以像这样指定文件路径作为命令行参数,如果没有指定,则从标准输入中读取内容,因此可以用管道连接grepsed

3. awk格式化输出

对这个问题而言,上一步其实已经足够了,但是打印格式会像这样

6 3173 1532344763332
9 3170 1532344768326
11 3168 1532344773326
// ...
28 3151 1532344828327
29 3150 1532344832624
2 3177 1532344848326
3 3176 1532344853314
// ...
338 2841 1532347936968
339 2840 1532347956968
2925 254 1532400463159
2917 262 1532402348131
2917 262 1532402509929

awk能读取以分隔符(默认是空格)分隔的字符串,然后print打印出来,更强大的是awk也可以用C风格的printf,因此命令如下

awk '{printf "%5d %4d %d\n", $1, $2, $3}' res

格式化参数就不说了,$1$2$3即该行分隔后的第1个、第2个、第3个参数。这里得强调了,$0代表一整行(不包含换行符)。

4. 封装成python脚本

#!/your-python-dir/python3
# analysis_log.py
import os
import sys

def usage(filename):
    print('usage: %s log-file [window-size]' % filename)
    os._exit(1)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        usage(sys.argv[0])

    if sys.argv[1] == '-h' or sys.argv[1] == '--help':
        usage(sys.argv[0])

    command = 'cat ' + sys.argv[1] + ' | grep "\[XYZ\].\+Offset" | sed ' \
        + "'s/.*Offset: \([[:digit:]]\+\) Lag: \([[:digit:]]\+\) Timestamp: \([[:digit:]]\+\).*/\\1 \\2 \\3/'" \
        + \
''' \
| awk '{printf "%5d %4d %d\\n", $1, $2, $3}' \
''' \

    if len(sys.argv) > 2:
        command = command + " | tail -n " + sys.argv[2]

#    print("# " + command)
    os.system(command)

其实用Python本身也能做,这里Python仅仅是简单解析命令行参数,因为模板字符串既带有单引号又带有双引号,用Python这种。最初是想采用str.format()方法,但是发现模板字符串中带%d解析起来有点坑,于是干脆算了,直接拼接字符串。
执行方式

python3 analysis_log.py

看起来比shell和可执行程序麻烦不少,毕竟每次都要敲python3这几个字符,而不能直接./analysis_log.py允许。实际上是可以的,但是这样的话第1行#!/your-python-dir/python3就必不可少,这一行必须以#!(注意不能有空格)开始。
这里我为了不透露自己的HOME目录(包含我的私密信息),所以用了your-python-dir代替,实际上是要指明解释器的路径,参考我机器上python解释器和shell解释器的信息

$ file /bin/bash
/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
$ file ~/local/python3.6.0/bin/python3.6
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not strippe

这是我自己编译安装的Python3.6,所以不像大多数代码指定的默认安装目录#!/usr/bin/python3
更详细的内容参考《Unix环境高级编程》8.12 解释器文件。
总之这就具备了直接执行的准备条件,只要给该脚本加上可执行权限即可

$ ./analysis_log.py --help
-bash: ./analysis_log.py: Permission denied
$ chmod u+x analysis_log.py 
$ ./analysis_log.py --help
usage: ./analysis_log.py log-file [window-size]

此外,grepsedawk的选项也非常多,实际使用时再参考Linux手册然后积累经验,不必死记硬背。看英文累的也可以直接搜索用法,结果应该是比较多的。

你可能感兴趣的:(grep+sed+awk简单实战,日志分析示例)