ELK技术栈 - logstash学习笔记(四)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: ELK技术栈 - logstash学习笔记(四)

编码插件(Codec)


Codec 是 logstash 从 1.3.0 版开始新引入的概念(Codec 来自 Coder/decoder 两个单词的首字母缩写)。

在此之前,logstash 只支持纯文本形式输入,然后以过滤器处理它。但现在,我们可以在输入 期处理不同类型的数据,这全是因为有了 codec 设置。

所以,这里需要纠正之前的一个概念。Logstash 不只是一个input | filter | output 的数据流,而是一个 input | decode | filter | encode | output 的数据流!codec 就是用来 decode、encode 事件的。

codec 的引入,使得 logstash 可以更好更方便的与其他有自定义数据格式的运维产品共存,比如 graphite、fluent、netflow、collectd,以及使用 msgpack、json、edn 等通用数据格式的其他产品等。

事实上,我们在第一个 "hello world" 用例中就已经用过 codec 了 —— rubydebug 就是一种 codec!虽然它一般只会用在 stdout 插件中,作为配置测试或者调试的工具。

小贴士:这个五段式的流程说明源自 Perl 版的 Logstash (后来改名叫 Message::Passing 模块)的设计。本书最后会对该模块稍作介绍。


采用 JSON 编码



在早期的版本中,有一种降低 logstash 过滤器的 CPU 负载消耗的做法盛行于社区(在当时的 cookbook 上有专门的一节介绍):直接输入预定义好的 JSON 数据,这样就可以省略掉 filter/grok 配置!

这个建议依然有效,不过在当前版本中需要稍微做一点配置变动 —— 因为现在有专门的 codec 设置。


配置示例


社区常见的示例都是用的 Apache 的 customlog。不过我觉得 Nginx 是一个比 Apache 更常用的新型 web 服务器,所以我这里会用 nginx.conf 做示例:


logformat json '{"@timestamp":"$time_iso8601",'
               '"@version":"1",'
               '"host":"$server_addr",'
               '"client":"$remote_addr",'
               '"size":$body_bytes_sent,'
               '"responsetime":$request_time,'
               '"domain":"$host",'
               '"url":"$uri",'
               '"status":"$status"}';
access_log /var/log/nginx/access.log_json json;


注意:在 requesttime和request_time 和 requesttimebody_bytes_sent 变量两头没有双引号 ",这两个数据在 JSON 里应该是数值类型!

重启 nginx 应用,然后修改你的 input/file 区段配置成下面这样:


input {
    file {
        path => "/var/log/nginx/access.log_json""
        codec => "json"
    }
}


运行结果


下面访问一下你 nginx 发布的 web 页面,然后你会看到 logstash 进程输出类似下面这样的内容:


{
      "@timestamp" => "2014-03-21T18:52:25.000+08:00",
        "@version" => "1",
            "host" => "raochenlindeMacBook-Air.local",
          "client" => "123.125.74.53",
            "size" => 8096,
    "responsetime" => 0.04,
          "domain" => "www.domain.com",
             "url" => "/path/to/file.suffix",
          "status" => "200"
}


小贴士


对于一个 web 服务器的访问日志,看起来已经可以很好的工作了。不过如果 Nginx 是作为一个代理服务器运行的话,访问日志里有些变量,比如说 $upstream_response_time,可能不会一直是数字,它也可能是一个 "-" 字符串!这会直接导致 logstash 对输入数据验证报异常。

有两个办法解决这个问题:

  1. sed 在输入之前先替换 -0

运行 logstash 进程时不再读取文件而是标准输入,这样命令就成了下面这个样子:


tail -F /var/log/nginx/proxy_access.log_json \
    | sed 's/upstreamtime":-/upstreamtime":0/' \
    | /usr/local/logstash/bin/logstash -f /usr/local/logstash/etc/proxylog.conf


  1. 日志格式中统一记录为字符串格式(即都带上双引号 "),然后再在 logstash 中用 filter/mutate 插件来变更应该是数值类型的字符字段的值类型。

有关 LogStash::Filters::Mutate 的内容,本书稍后会有介绍。


合并多行数据(多)



有些时候,应用程序调试日志会包含非常丰富的内容,为一个事件打印出很多行内容。这种日志通常都很难通过命令行解析的方式做分析。

而logstash正为此准备好了codec / multiline插件!

小贴士:multiline插件也可以用于其他类似的堆栈式信息,比如linux的内核日志。


配置示例


input {
    stdin {
        codec => multiline {
            pattern => "^\["
            negate => true
            what => "previous"
        }
    }
}


运行结果


运行logstash进程,然后在等待输入的终端中输入如下几行数据:


[Aug/08/08 14:54:03] hello world
[Aug/08/09 14:54:04] hello logstash
    hello best practice
    hello raochenlin
[Aug/08/10 14:54:05] the end


你会发现logstash输出下面这样的返回:


{
    "@timestamp" => "2014-08-09T13:32:03.368Z",
       "message" => "[Aug/08/08 14:54:03] hello world\n",
      "@version" => "1",
          "host" => "raochenlindeMacBook-Air.local"
}
{
    "@timestamp" => "2014-08-09T13:32:24.359Z",
       "message" => "[Aug/08/09 14:54:04] hello logstash\n\n    hello best practice\n\n    hello raochenlin\n",
      "@version" => "1",
          "tags" => [
        [0] "multiline"
    ],
          "host" => "raochenlindeMacBook-Air.local"
}


你看,后面这个事件,在“message”字段里存储了三行数据!

小贴士:你可能注意到输出的事件中都没有最后的“the end”字符串。这是因为你最后输入的回车符\n并不匹配设定的^[正则表达式,logstash还得等下一行数据直到匹配成功后才会输出这个事件。


解释


其实这个插件的原理很简单,就是把当前行的数据添加到前面一行后面,,新直到进的当前行匹配^\[正则为止。

这个正则还可以用grok表达式,稍后你就会学习这方面的内容。


Log4J的另一种方案


说到应用程序日志,log4j肯定是第一个被大家想到的。使用codec/multiline也确实是一个办法。

不过,如果你本事就是开发人员,或者可以推动程序修改变更的话,logstash还提供了另一种处理log4j的方式:input / log4j。与codec/multiline不同,这个插件是直接调用了org.apache.log4j.spi.LoggingEvent处理TCP端口接收的数据。


推荐阅读


github.com/elasticsear…


Grok 正则捕获



Grok 是 Logstash 最重要的插件。你可以在 grok 里预定义好命名正则表达式,在稍后(grok参数或者其他正则表达式里)引用它。


正则表达式语法


运维工程师多多少少都会一点正则。你可以在 grok 里写标准的正则,像下面这样:


\s+(?<request_time>\d+(?:\.\d+)?)\s+


小贴士:这个正则表达式写法对于 Perl 或者 Ruby 程序员应该很熟悉了,Python 程序员可能更习惯写 (?Ppattern),没办法,适应一下吧。

现在给我们的配置文件添加第一个过滤器区段配置。配置要添加在输入和输出区段之间(logstash 执行区段的时候并不依赖于次序,不过为了自己看得方便,还是按次序书写吧):


input {stdin{}}
filter {
    grok {
        match => {
            "message" => "\s+(?<request_time>\d+(?:\.\d+)?)\s+"
        }
    }
}
output {stdout{}}


运行 logstash 进程然后输入 "begin 123.456 end",你会看到类似下面这样的输出:


{
         "message" => "begin 123.456 end",
        "@version" => "1",
      "@timestamp" => "2014-08-09T11:55:38.186Z",
            "host" => "raochenlindeMacBook-Air.local",
    "request_time" => "123.456"
}


漂亮!不过数据类型好像不太满意……request_time 应该是数值而不是字符串。

我们已经提过稍后会学习用 LogStash::Filters::Mutate 来转换字段值类型,不过在 grok 里,其实有自己的魔法来实现这个功能!


Grok 表达式语法


Grok 支持把预定义的 grok 表达式 写入到文件中,官方提供的预定义 grok 表达式见:github.com/logstash/lo…

注意:在新版本的logstash里面,pattern目录已经为空,最后一个commit提示core patterns将会由logstash-patterns-core gem来提供,该目录可供用户存放自定义patterns

下面是从官方文件中摘抄的最简单但是足够说明用法的示例:


USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}


第一行,用普通的正则表达式来定义一个 grok 表达式;第二行,通过打印赋值格式,用前面定义好的 grok 表达式来定义另一个 grok 表达式。

grok 表达式的打印复制格式的完整语法是下面这样的:


%{PATTERN_NAME:capture_name:data_type}


小贴士:data_type 目前只支持两个值:int 和 float。

所以我们可以改进我们的配置成下面这样:


filter {
    grok {
        match => {
            "message" => "%{WORD} %{NUMBER:request_time:float} %{WORD}"
        }
    }
}


重新运行进程然后可以得到如下结果:


{
         "message" => "begin 123.456 end",
        "@version" => "1",
      "@timestamp" => "2014-08-09T12:23:36.634Z",
            "host" => "raochenlindeMacBook-Air.local",
    "request_time" => 123.456
}


这次 request_time 变成数值类型了。


最佳实践


实际运用中,我们需要处理各种各样的日志文件,如果你都是在配置文件里各自写一行自己的表达式,就完全不可管理了。所以,我们建议是把所有的 grok 表达式统一写入到一个地方。然后用 filter/grokpatterns_dir 选项来指明。

如果你把 "message" 里所有的信息都 grok 到不同的字段了,数据实质上就相当于是重复存储了两份。所以你可以用 remove_field 参数来删除掉 message 字段,或者用 overwrite 参数来重写默认的 message 字段,只保留最重要的部分。

重写参数的示例如下:


filter {
    grok {
        patterns_dir => "/path/to/your/own/patterns"
        match => {
            "message" => "%{SYSLOGBASE} %{DATA:message}"
        }
        overwrite => ["message"]
    }
}


小贴士



多行匹配

在和 codec/multiline 搭配使用的时候,需要注意一个问题,grok 正则和普通正则一样,默认是不支持匹配回车换行的。就像你需要 =~ //m 一样也需要单独指定,具体写法是在表达式开始位置加 (?m) 标记。如下所示:


match => {
    "message" => "(?m)\s+(?<request_time>\d+(?:\.\d+)?)\s+"
}


多项选择


有时候我们会碰上一个日志有多种可能格式的情况。这时候要写成单一正则就比较困难,或者全用 | 隔开又比较丑陋。这时候,logstash 的语法提供给我们一个有趣的解决方式。

文档中,都说明 logstash/filters/grok 插件的 match 参数应该接受的是一个 Hash 值。但是因为早期的 logstash 语法中 Hash 值也是用 [] 这种方式书写的,所以其实现在传递 Array 值给 match 参数也完全没问题。所以,我们这里其实可以传递多个正则来匹配同一个字段:


match => [
    "message", "(?<request_time>\d+(?:\.\d+)?)",
    "message", "%{SYSLOGBASE} %{DATA:message}",
    "message", "(?m)%{WORD}"
]


logstash 会按照这个定义次序依次尝试匹配,到匹配成功为止。虽说效果跟用 | 分割写个大大的正则是一样的,但是可阅读性好了很多。

最后也是最关键的,我强烈建议每个人都要使用 Grok Debugger 来调试自己的 grok 表达式。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
消息中间件 缓存 Java
ELK技术栈 - logstash学习笔记(九)
ELK技术栈 - logstash学习笔记(九)
75 0
|
6月前
|
SQL JSON API
ELK技术栈 - Elasticsearch 学习笔记(三)
ELK技术栈 - Elasticsearch 学习笔记(三)
103 0
|
6月前
|
存储 编解码 自然语言处理
ELK技术栈 - logstash学习笔记(七)
ELK技术栈 - logstash学习笔记(七)
96 0
|
6月前
|
消息中间件 监控 网络协议
ELK技术栈 - logstash学习笔记(二)
ELK技术栈 - logstash学习笔记(二)
65 0
|
6月前
|
运维 Java Linux
ELK技术栈 - logstash学习笔记(一)
ELK技术栈 - logstash学习笔记(一)
99 0
|
6月前
|
存储 监控 NoSQL
ELK技术栈 - logstash学习笔记(八)
ELK技术栈 - logstash学习笔记(八)
58 0
|
6月前
|
JSON 编解码 运维
ELK技术栈 - logstash学习笔记(六)
ELK技术栈 - logstash学习笔记(六)
60 0
|
6月前
|
存储 JSON NoSQL
ELK技术栈 - logstash学习笔记(三)
ELK技术栈 - logstash学习笔记(三)
67 0
|
6月前
|
编解码 JSON Java
ELK技术栈 - logstash学习笔记(四)
ELK技术栈 - logstash学习笔记(四)
91 0
|
6月前
|
编解码 Unix 应用服务中间件
ELK技术栈 - logstash学习笔记(五)
ELK技术栈 - logstash学习笔记(五)
65 0
下一篇
无影云桌面