Logstash在处理数据的时候,会自动生成一个字段@timestamp,默认该字段存储的是Logstash收到消息/事件(event)的时间。很多时候我们用ELK是处理日志的,日志里面一般都是有时间的。而且很多时候我们只关注日志里面的时间,而不关注Logstash收到这条日志的时间。这个时候,一种方法是再增加一个字段,用来存储日志里面的时间,这种很简单;另一种方法是使用日志中的时间替换掉@timestamp字段默认的时间。本文介绍第二种方法并总结一些关键知识点。

现在有如下一条日志:

2018-02-26 15:48:32.708-[INFO ] main RestfulApiProvider - initializing restful api provider

我们先用最简单的Logstash规则解析,规则文件test.conf如下:

# 从标准输入读入数据
input { stdin {} }            

# 只解析时间戳,其它信息不管
filter {
    grok {
        match => {  "message" => "(?<timestamp>%{TIMESTAMP_ISO8601})"  }
    }
}

# 输出到标准输出
output {
 stdout { codec => rubydebug }
}

启动Logstash:bin/logstash -f test.conf

在标准输入粘贴上面的日志并回车,输出如下:

{
    "host" => "NiYanchuns-MacBook-Air.local",
    "@timestamp" => 2018-10-18T12:20:51.603Z,
    "timestamp" => "2018-02-26 15:48:32.708",
    "message" => "2018-02-26 15:48:32.708-[INFO ] main RestfulApiProvider - initializing restful api provider",
    "@version" => "1"
}

这里需要注意以下几个点:

  • 解析规则里面的grok是Logstash的一个正则解析插件,Logstash强大的解析能力主要来自于它,使用文档看这里
  • @timestamp字段是内置的,和之前说的一样,时间是Logstash收到消息的时间,而且注意使用的是UTC时间,我电脑的时间是北京时间晚上八点多。
  • timestamp字段是我们解析规则里面定义的字段(字段名随便起,不能有特殊符号,比如@),该字段存储的是从日志里面解析出来的时间,注意这个时间格式完全是日志里面时间的格式。要注意我们加的timestamp和系统内置的@timestamp不是同一个字段。
  • message:也是程序内置字段,内容为消息原始内容。

既然如此,那我们能不能通过自定义一个与程序内置的@timestamp同名的字段来覆盖掉程序内置字段呢?比如将上面解析规则里面的timestamp改为@timestamp

答案是不行。刚才已经说了,系统内置字段名前可以加@,但我们定义字段的时候字段名不能加@,也就是说我们不能通过定义一个@timestamp字段覆盖掉系统默认的,那样配置语法检查就通不过。有(bu)兴(xin)趣(xie)的可以试一下。

那如何做呢?Logstash提供了一个Date插件可以实现该功能,使用文档见这里。我们修改一下刚才的规则:

input { stdin {} }

filter {
    grok {
        match => {  "message" => "(?<timestamp>%{TIMESTAMP_ISO8601})"  }
    }
    date {
        match => [ "timestamp", "ISO8601" ]
    }
}

output {
 stdout { codec => rubydebug }
}

重启Logstash后,输入日志后,解析输出如下:

{
    "@version" => "1",
    "host" => "NiYanchuns-MacBook-Air.local",
    "timestamp" => "2018-02-26 15:48:32.708",
    "@timestamp" => 2018-02-26T07:48:32.708Z,
    "message" => "2018-02-26 15:48:32.708-[INFO ] main RestfulApiProvider - initializing restful api provider"
}

OK,@timestamp字段里面的值已经和timestamp字段很像了,但不完全一样。Logstash将timestamp的时间根据系统的时区转换为UTC时间存到了@timestamp字段里面。

这个时候timestamp就成多余的了,我们可以通过mutate插件移除该字段。再次修改解析规则文件:

input { stdin {} }

filter {
    grok {
        match => {  "message" => "(?<timestamp>%{TIMESTAMP_ISO8601})"  }
    }
    date {
        match => [ "timestamp", "ISO8601" ]
    }
    mutate {
         remove_field => [ "ts_tmp","date", "time", "end_time_tmp", "end_time_tmp1" ]
    }
}

output {
 stdout { codec => rubydebug }
}

重启Logstash后,输入日志后,解析输出如下:

{
    "message" => "2018-02-26 15:48:32.708-[INFO ] main RestfulApiProvider - initializing restful api provider",
    "@version" => "1",
    "@timestamp" => 2018-02-26T07:48:32.708Z,
    "host" => "NiYanchuns-MacBook-Air.local"
}

timestamp字段已经在输出中去掉了。

我们简单介绍一下Date插件。Date常见的配置如下:

date {
    match => [ "time_field", "yyyyMMdd HH:mm:ss.SSS" ]
    # timezone => "UTC"
    target => "end_time"
}

上述配置的含义是,将time_field字段按照yyyyMMdd HH:mm:ss.SSS格式解析后存到target指定的字段end_time字段去。time_field必须是已经定义的字段,最常见的就是在grok里面解析出来的某个时间字段。时间格式可查看Date插件的文档。如果没有指定target,默认就是@timestamp字段,这就是为什么我们可以使用该插件来修改@timestamp字段值的原因。

另外,timezone字段在某些场景下也非常重要,如果从时间的值里面解析不出来时区,而且我们也没有指定时区的话,程序就会认为我们的时间字段的时区就是系统所处时区。比如上面从timestamp转到@timestamp的时候,时间值里面没有时区,所以使用了系统的时区东八区。当然,我们可以使用该字段指定时区。

OK,本文就到这里,干(hong)正(wo)事(wa)去了。