知识分享
Log 文件解析

本次讨论的日志范围


日志在计算机系统中是一个非常广泛的概念,任何程序都有可能输出日志:操作系统内核、各种应用服务器等等。日志的格式也没有限定,有些为文本文件,有些为特殊的二进制格式如 Windows 日志。这里讨论的日志处理方法仅指文本文件格式的日志,如Web服务器 IIS、Tomcat等产生的用户访问日志,以及各种Web应用程序自己输出的日志等。这些日志还有一个共同的特点就是其为半结构化数据,可以使用模式匹配的方式将其转化为结构化数据。


为什么要解析日志


以Web日志为例,Web日志中包含了大量有价值的信息。最简单的,我们可以从中获取网站每类页面的PV值(PageView,页面访问量)、独立IP数(即去重之后的IP数量)等;稍微复杂一些的,可以计算得出用户所检索的关键词排行榜、用户停留时间最高的页面等;更复杂的,构建广告点击模型、分析用户行为特征等等。

另外还有一类产品,它们不分析直接日志,而是通过让用户在页面中嵌入js代码的方式来直接进行数据统计,或者说我们可以认为它是直接让日志输出到了它们的服务器。典型的代表产品——大名鼎鼎的Google Analytics,另外还有国内的cnzz、百度统计等。但是这些产品提供的功能都相对比较通用,一般只限于上面说到的PV, UV, 关键词排行之类的,当有特殊需求时还需要自己来完成日志的解析工作来实现。

LogStash


Logstash 是开源的服务器端数据处理管道,能够同时 从多个来源采集数据、转换数据,然后将数据发送到您最喜欢的 “存储库” 中。Logstash 能够动态地转换和解析数据,不受格式或复杂度的影响:

  • 利用 Grok 从非结构化数据中派生出结构

  • 从 IP 地址破译出地理坐标

  • 将 PII 数据匿名化,完全排除敏感字段

  • 整体处理不受数据源、格式或架构的影响

过滤器库丰富多样,拥有无限可能。

使用Docker来尝试一下:

首先,拉取开源版的logstash最新版:

$ docker pull docker.elastic.co/logstash/logstash-oss:6.3.0然后,运行这个image$ docker run --rm -it -v ~/pipeline/:/usr/share/logstash/pipeline/ docker.elastic.co/logstash/logstash-oss:6.3.0



--rm 表示容器退出后自动删除

-it 表示以终端交互式运行

-v 表示把本机上~/pipleline目录映射到容器内的/usr/share/logstash/pipeline目录,这个目录为logstash的运行配置文件所在的目录。程序运行时会自动从这个目录加载配置文件并执行。

docker.elastic.co/logstash/logstash-oss:6.3.0 为镜像名称

我们使用以下配置来运行命令

input {
 stdin { }  
} filter {
 grok {    match => { "message" => "%{TIMESTAMP_ISO8601:datetime} %{IP:s-ip} %{WORD:cs-method} %{URIPATH:cs-uri-stem} %{NOTSPACE:cs-uri-query} %{NUMBER:s-port} %{NOTSPACE:cs-username} %{IP:c-ip} %{NOTSPACE:userAgent} %{NOTSPACE:cookie} %{NOTSPACE:referer} %{NUMBER:sc-status} %{NUMBER:sc_substatus} %{NUMBER:sc_winstatus} %{NUMBER:sc-bytes} %{NUMBER:cs-bytes} %{NUMBER:time-taken}" }  } 
} output {
 stdout {} 
}

在input中使用stdin即键盘输出,在output中使用stdout即屏幕。在filter中使用grok并给定了表达式。这个表达式最终会变成正则表达式来匹配输入。

表过式的语法为%{正则或已定义好的模式:标识符},通过这样的转换我们就把难以维护的正则表达式都进行命名,再将匹配组进行标识化,大大提高了正则匹配可维护度。

在以下目录中可以找到系统定义好的pattern,目录中的版本号可能有所不同:/logstash-6.2.4/vendor/bundle/jruby/2.3.0/gems/logstash-patterns-core-4.1.2/patterns。如本例中使用的TIMESTAMP_ISO8601,IP等就定义在grok-patterns文件中。

在测试时可以使用以下这个日志行来进行:2013-03-21 15:59:59 27.54.194.244 GET /data-source/acc/3d/10026.swf - 80 - 120.83.65.98 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+6.1;+WOW64;+Trident/4.0;+QQDownload+718;+SLCC2;+.NET+CLR+2.0.50727;+.NET+CLR+3.5.30729;+.NET+CLR+3.0.30729;+Media+Center+PC+6.0;+.NET4.0C;+.NET4.0E) Hm_lpvt_dd614ea78861a7710d8659f866d5c807=1363881587;+pgv_si=s9497496576;+Hm_lvt_dd614ea78861a7710d8659f866d5c807=1363763148,1363765488,1363765519,1363880532;+pgv_pvi=714221568 http://www.sdgundam.cn/unit/10026 200 0 0 34640 630 270通过grok插件logstash已经将日志转化成了如下结构化数据:

image.png


Grok


如果我们想在自己的项目中也使用grok,来完成一些半结构化数据的处理。可以尝试这个开源项目:

https://github.com/thekrakken/java-grok

    public static void main(String[] args) {
       /* Create a new grokCompiler instance */
       GrokCompiler grokCompiler = GrokCompiler.newInstance();
       grokCompiler.registerDefaultPatterns();
       /* Grok pattern to compile, here httpd logs */
       final Grok grok = grokCompiler.compile("%{TIMESTAMP_ISO8601:datetime} %{IP:s-ip} %{WORD:cs-method} %{URIPATH:cs-uri-stem} %{NOTSPACE:cs-uri-query} %{NUMBER:s-port} %{NOTSPACE:cs-username} %{IP:c-ip} %{NOTSPACE:userAgent} %{NOTSPACE:cookie} %{NOTSPACE:referer} %{NUMBER:sc-status} %{NUMBER:sc_substatus} %{NUMBER:sc_winstatus} %{NUMBER:sc-bytes} %{NUMBER:cs-bytes} %{NUMBER:time-taken}");
       /* Line of log to match */
       String log = "2013-03-21 15:59:59 27.54.194.244 GET /data-source/acc/3d/10026.swf - 80 - 120.83.65.98 Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+6.1;+WOW64;+Trident/4.0;+QQDownload+718;+SLCC2;+.NET+CLR+2.0.50727;+.NET+CLR+3.5.30729;+.NET+CLR+3.0.30729;+Media+Center+PC+6.0;+.NET4.0C;+.NET4.0E) Hm_lpvt_dd614ea78861a7710d8659f866d5c807=1363881587;+pgv_si=s9497496576;+Hm_lvt_dd614ea78861a7710d8659f866d5c807=1363763148,1363765488,1363765519,1363880532;+pgv_pvi=714221568 http://www.sdgundam.cn/unit/10026 200 0 0 34640 630 270";
       Match gm = grok.match(log);
       /* Get the map with matches */
       final Map capture = gm.capture();
       capture.forEach((k, v) -> {
           System.out.println(String.format("%s = %s", k, v));
       });
   }