问题介绍
通常,日志文件都是文本格式,其内容是非结构化的文本串。这就使得我们查询日志信息时,一般只能使用文本编辑软件的搜索功能,输入关键字后,靠眼力去侦查每处匹配结果。在日志量不大,或者只是偶尔查一下时,这么操作倒也无妨。不过,再简单的事情也怕多次重复。如果需要频繁查询,量变就可能引起质变。如果每次还都要靠人工搜索,那么就算有再好的视力,也会有头晕目眩的时候。因此,想要轻松查询日志,就必须找到一款合适的工具,有了合适的工具,就可以一边喝着咖啡,一边轻弹条件回车就行了。
工具里面,首先想到的,就是利用各种计算机开发语言,外加关系数据库。但这类工具开发过程繁琐,还需要准备好多工作环境,包括配置语言开发环境,安装数据库服务,安装数据库查询应用等。
对于这么“重”的方案,我们果断撇开。因为今天要介绍一个轻巧方便的工具——集算器,利用集算器,可以将文本日志变成结构化数据,然后就可以使用我们熟悉的 SQL 式查询了。
这里,我们利用到了集算语言 (Structured Process Language,简称 SPL) 的两大优点:
- 将日志内容结构化为数据表结构,SPL 远比常用开发语言简单、易用、直观。
- SPL 支持直接对结构化的文件进行 SQL 查询,不再需要安装配置第三方数据库软件。
下面我们就来了解一下具体的实施过程。
单行日志结构化为一条记录
1、日志结构分析
不同的日志文件,其内容格式五花八门,每一个看上去都杂乱无章。但对于某个特定的具体的日志来说,它一定会有它自己的结构。拿到日志文件后,首先要做的就是分析日志内容,提炼数据结构,总结出可以结构化的字段。
作为示例,我们用腾讯视频软件下的一个启动日志来做案例。如果你也用过腾讯视频,就可以利用下面的代码来体验和学习,分析一下自己的使用行为了。这个日志文件,位于当前用户的 AppData 路径下,并且以 QQLive.exe[Main] 开头。在我的机器上,这个文件就是:
C:\Users\[Joancy]\AppData\Roaming\Tencent\QQLive\Log\QQLive.exe[Main][2018-8-3 21-5-35-557][12164].log
上述路径中 [Joancy] 是我的 Windows 登录用户名,在你的机器中,将会是你的用户名。QQLive.exe[Main]开头的日志文件有很多,随便取一个就可以。
下面就是这个日志文件中的两行:
[18-07-19 14:35:06][9416]-[31ms][QQLiveMainModule.dll][CQQLiveModule::ParsCommandLine] cmd=”C:\Program Files (x86)\Tencent\QQLive\QQLive.exe” -system_startup
[18-07-19 14:35:08][9416]-[2266ms][HttpModule.dll][CDownloadMgr::AddTask]keyid = 1,url = http://182.254.116.117/d?dn=vv.video.qq.com.&ttl=1
可以看到,这个日志的内容比较规整,一行一条记录。每行中一对中括号中的内容为一节,对应一个字段。只是最后的两节有点特殊,其中倒数第二节可以省略,而最后一节没用中括号括起来。这样,我们就可以整理出日志表的数据结构如下,并且把第一行内容作为对应的示例:
字段名 |
类型 |
分节内容 |
|
1 |
记录时间 |
DateTime |
[18-07-19 14:35:06] |
2 |
线程编号 |
Integer |
[9416] |
3 |
加载时刻 |
Integer |
[31ms] |
4 |
加载模块 |
String |
[QQLiveMainModule.dll] |
5 |
加载函数 |
String |
[CQQLiveModule::ParsCommandLine] |
6 |
日志内容 |
String |
cmd=”C:\Program Files (x86)\Tencent\QQLive\QQLive.exe” -system_startup |
表(1)
解析各个字段时,需要注意:
1) 记录时间: 由于年份只有两位,所以在转成日期时间类型时,需要指定相匹配的日期格式,否则 18 就会被当成公元 18 年,而不是 2018 年了。具体的操作方法是打开集算器菜单中的选项,在弹出的窗口中点击环境页面,设置属性‘日期时间格式’为‘yy-MM-dd HH??ss’。
2) 加载时刻: 描述的是程序自从启动后,当前模块从启动后历经的毫秒数。由于字串后面有单位‘ms’,因此需要在转换为整数前先去掉这个单位。
3) 加载函数: 这个字段不一定有,所以需要在分析结构时进行判断,没有该字段时,需要插入空值。
2、结构化实施
如果采用数据库来存储结构化后的日志数据,首先需要安装和配置数据库,然后安装合适的数据库查询应用程序,这个过程可能会比较麻烦。如果利用现成的生产数据库,那么首先可能更新不方便,其次更重要的,会影响生产系统的性能。
而利用集算器,由于 SPL 支持直接对结构化的文件进行 SQL 查询,那就简单了,只需将结构化后的内容保存到文件就行了。
下面开始编程。在集算器设计器中,新建文件,设定它的网格参数为‘fileName’,用于指定需要转换的日志文件,然后把文件保存为 convert.dfx。具体的实现脚本如下:
A |
B |
C |
D |
|
1 |
=create(记录时间, 线程编号, 加载时刻, 加载模块, 加载函数, 日志内容) |
|||
2 |
=file@s(fileName:”UTF-8″) |
=A2.read@n() |
||
3 |
for B2 |
if len(trim(A3))==0 |
next |
|
4 |
=[] |
|||
5 |
for 4 |
=substr@l(A3,”]”) |
>A3=substr(A3,”]”) |
|
6 |
=substr(C5,”[“) |
|||
7 |
if B5==3 |
>C6=substr@l(C6,”ms”) |
||
8 |
=parse(C6) |
=B4=B4|C8 |
||
9 |
if left(A3,1)==”[“ |
=substr@l(A3,”]”) |
>A3=substr(A3,”]”) |
|
10 |
=substr(C9,”[“) |
=parse(C10) |
||
11 |
=B4=B4|D10 |
|||
12 |
else |
=B4.insert(5,null) |
||
13 |
=B4=B4|A3 |
|||
14 |
>A1.record(B4) |
|||
15 |
=A2.name() |
=filename@d(A15) |
=filename@n(A15) |
|
16 |
=outFile=B15+”\\”+C15+”.txt” |
|||
17 |
>file(outFile).export@t(A1) |
|||
18 |
>output(“转换为 “+outFile+” 成功。”) |
表(2)
这段脚本的功能就是接收日志文件,将文本内容按行读取为一个序表。然后逐行拆分信息成为一条记录。最后将转换后的序表保存到同目录下另一个文件。脚本中用到最多的一个函数是 substr(src,target),作用是在 src 中找 target ,找到后返回 src 中 target 后面(右边)的字串,找不到时返回空。如果使用选项 @l 时,则返回前面(左边)的字串。注意:文中表格的合并格,仅仅是为了阅读方便。集算器网格没有合并格,将代码复制到集算器网格中时,只能对应到合并格的首格。
脚本详解:
1)A1 格的 create 函数类似数据库的 Create Table 命令,作用是创建一个空表,字段名就是 create 函数的参数,但不需要事先指定数据类型。
2)A2 格使用 file 函数,用选项 @s 加载位于寻址路径下的 fileName 文件,通过参数”UTF-8”指明该日志文件是 UTF-8 字符集的,且包含了中文(如果中文出现乱码,那就是字符集不对)。
3)B2 将文件内容读入到序列,选项 @n 表示读取的内容是文本。
4)A3 为循环,依次遍历 B2 的成员。
5)B3 和 C3 用于跳过空行。
6)B4 初始化一个空序列,用于容纳解析后的每个字段值。
7)B5 先循环 4 次,分离前 4 个字段内容。这是因为每一行日志仅有前 4 节由‘[]’分开,而第 5 节如果有‘[]’,则其中内容解析为‘加载函数’,如果没有就将后面的所有信息解析为‘日志内容’。同时,之所以不是一次就按中括号全部拆分,是因为日志内容里面仍然可能包含‘[]’,所以随后的两个字段需要单独解析。