预计阅读时间:7分钟
由于Oracle没有MySQL或EDB慢日志这种机制,因此最近有一个运维的需求,需要解析应用的日志,进行SQL语句执行时间的统计,进而进行监控。
每个应用集群中的节点,会生成自己的处理日志,日志采用循环复写的机制,单个文件512MB,一天最多产生10个,日志文件中待检索的格式,模拟如下,
第一行:时间戳(yyyymmdd-hh24miss.sss-唯一交易ID
第二行:备注信息
第三行:SQL语句原文/SQL参数/开始/结束标识
虽然某一时刻,最多可能有10个日志,但由于选择5-10分钟采集一次,考虑到系统并发的业务需求,因此这段时间内产生多个新日志的可能性并不是很高,所以打算采用抽样的方法,每次到达采集的时间点,只选择解析最新的一个日志,同时,为了降低对应用节点,正常业务的影响,采用分布处理的方法,应用节点上会扫描日志,生成中间日志,管理节点定期采集中间日志,在管理节点上,进行统计和分析,思路如下:
这里先考虑第一步操作,即如何生成中间日志。
下面是一个原始日志的例子:
20180601-120000.100-10000
TEST1
START
...
20180601-120000.150-10000
TEST1
SQL[SELECT * FROM A WHERE ID = ?]
...
20180601-120000.200-10000
TEST1
PARA[1]
...
20180601-120000.300-10000
TEST1
END
已知:
提供了“SQL语句原文/SQL参数/开始/结束标识”的关键字,例如START、SQL、PARA和END。
第一行包含本次交易的唯一ID,用于关联本次交易的操作。
SQL执行时间,用END的时间-START的时间,上例中120000.300-120000.100=200毫秒。
除了上述这四种,由于多线程操作,所以日志中会有其他信息,穿插在这四种中,统计的时候,需要排除除上述信息外,其他的日志信息。
第一行包含本次交易的唯一ID,用于关联本次交易的操作。
除了上述这四种,由于多线程操作,所以日志中会有其他信息,穿插在这四种中,统计的时候,需要排除除上述信息外,其他的日志信息。
对于中间日志的生成,打算做到轻量级,因此选择了shell脚本,不依赖于任何第三方工具。
根据上面的需求描述,我们拆开来看,需要做几件事情,
按照时间排序,得到最新生成的日志名。
找出原始日志中,所有“SQL语句原文/SQL参数/开始/结束标识“相关的行,包括关键字所在的行,以及前两行,用于统计的时候,可以根据交易ID,将不同的操作串起来。
生成中间日志。
找出原始日志中,所有“SQL语句原文/SQL参数/开始/结束标识“相关的行,包括关键字所在的行,以及前两行,用于统计的时候,可以根据交易ID,将不同的操作串起来。
针对需求1,
ls -lt $AUDIT_HOME | grep app | head -n 1 | awk '{print $9}'
从思路上看,
ls可以列出所有的文件,使用lt,可以按照时间进行排序,示例如下,
bisal ls -lt
total 2
-rw-r--r-- 1 bisal staff 781 6 26 10:12 app2010010102.log
-rw-r--r-- 1 bisal staff 412 6 26 10:11 app2010010101.log
为了过滤total这行,又由于日志带有app关键字,可以使用grep过滤。
-rw-r--r-- 1 bisal staff 781 6 26 10:12 app2010010102.log
-rw-r--r-- 1 bisal staff 412 6 26 10:11 app2010010101.log
head -n选择第一个文件。
-rw-r--r-- 1 bisal staff 781 6 26 10:12 app2010010102.log
日志文件名在这行第九位,使用awk打印$9即可。
针对需求2,
grep -B 2 -E "START|SQL|PARA|END" 日志文件名
grep的B参数,可以帮我们找出,匹配关键字所在行之前N行,
-B num, --before-context=num
Print num lines of leading context before each match.
See also the -A and -C options.
真的很强大,一个grep -B,就能完成此需求,但其实一开始,走了一些弯路,当时考虑使用awk,记录前一行,再根据下一行,是否匹配关键字,判断打印这两行,可这种做法,无法打印第一行,即包含交易ID的行,
awk '/START/SQL/PARA/END/{print a" "$0}{a=$1}' 日志文件名
由于包含多个关键字,因此grep需要使用-E,
-E, --extended-regexp
Interpret pattern as an extended regular expression
(i.e. force grep to behave as egrep).
针对需求3,
这就很简单了,使用定向到本地文件中,
grep -B 2 -E "START|SQL|PARA|END" 日志文件名 中间日志文件名
这里的中间日志,仅包含这几个匹配的关键字以及前三行的日志内容,作为SQL执行时间解析的输入。
虽然shell很轻量,但要讲究起来,还是有很多技巧,例如,可以将一些文件名和路径,定义为变量,还可以为应用加上日志,以及操作时间戳。曾经借鉴研究了一些,可以参考《》。
以上完整的实现,示例如下:
#!/bin/bash
######################################################
# $Name: scan_log.sh
# $Version: v1.0
# $Function: Scan log to monitor SQL
# $Author: Bisal
# $Create Date: 2018-06-30
######################################################
HOME=/home/app
FILE_NAME=`ls -lt $HOME | grep app | head -n 1 | awk '{print $9}'`
LOG=$HOME/$FILE_NAME
OUTPUT_FILENAME=/home/app/audit.log
APP_LOG=/home/app/run.log
echo "$(date "+%Y-%m-%d") $(date "+%H-%M-%S"): Scan Start" ${APP_LOG}
grep -B 2 -E "START/SQL/PARA/END" $LOG $OUTPUT_FILENAME
echo "$(date "+%Y-%m-%d") $(date "+%H-%M-%S"): Completed" ${APP_LOG}
echo " " ${APP_LOG}
在此过程中,感谢饿了么的学明兄、京东方的Hurley以及Nasdaq-NYC的James的指教和建议。
如果您觉得本文有帮助,欢迎关注转发:bisal的个人杂货铺,