日志数据采集中的日期时间格式问题解决方案
最佳实践
按照数据流的顺序:
message: 2022-07-22T16:00:00.000+08:00--------带有分隔符T和时区
- 如果仅用于搜索
logstash配置:log_time和@timestamp都保留
grok { match => { "message" => "%{TIMESTAMP_ISO8601:log_time}" } } date { match => ["log_time","ISO8601"] }
解析结果
{ "message" => "2022-07-22T16:00:00.000+08:00", "log_time" => "2022-07-22T16:00:00.000+08:00", "@timestamp" => 2022-07-22T08:00:00.000Z -----减了8小时,带Z时区,是UTC标准时间 }
存到es的结果:
{ "message" => "2022-07-22T16:00:00.000+08:00", "log_time" => "2022-07-22T16:00:00.000+08:00", "@timestamp" => 2022-07-22T08:00:00.000Z -----减了8小时,带Z时区,是UTC标准时间 }
查询和返回:
以log_time显示的时间作为基准传值,用@timestamp这个字段查,返回的时候前端用log_time显示
- 如果需要去掉时区和T分隔符
filter { grok { match =>{ "message" => "(?<date>%{YEAR}-%{MONTHNUM}-%{MONTHDAY})[T ](%{TIME:time})" } add_field => { "log_time" => "%{date} %{time}" } } date { match => ["log_time","yyyy-MM-dd HH:mm:ss.SSS"] } }
解析结果
{ "message" => "2022-07-22T16:00:00.000+08:00", "date" => "2022-07-22", "time" => "16:00:00.000", "log_time" => "2022-07-22 16:00:00.000", "@timestamp" => 2022-07-22T08:00:00.000Z, }
过程分析
es关于date类型分析
另外,logstash-es生命周期需要读取@timestamp字段值,这个字段不能删除
审计中心logstash流程解析
conf-filter内容
filter { grok { match => { "message" => "(?<date>(?:\d\d){1,2}-(?:0?[1-9]|1[0-2])-(?:(?:0?[1-9])|(?:[12][0-9])|(?:3[01])))T%{TIME:time}\+08:00%{SPACE}%{LOGLEVEL:logLevel}%{SPACE}(?<componentId>[a-zA-Z0-9]+)\.(?<segment>[a-zA-Z0-9]+)%{SPACE}\[(?<threadName>[a-zA-Z0-9-]*)\]%{SPACE}\[(?<stackTrace>[a-zA-Z\.]+:\d+)\]%{SPACE}\-(?<log_data>.*)" } add_field => { "timestamp" => "%{date} %{time}" } } date { match => ["timestamp","yyyy-MM-dd HH:mm:ss.SSS"] target => "@timestamp" ------这个是默认值,可以不指定 timezone => "Asia/Shanghai" ------读取时间值时候的时区,默认是当前系统时区,不用设置,这个不是指logstash存储时候的时区,存储都是用的UTC标准时间 } }
结论和建议:grok解析时间的地方改为 (?<date>%{YEAR}-%{MONTHNUM}-%{MONTHDAY})[T ](%{TIME:time})
解析结果示例
结论
message时间解析出来后去掉字符T和时区给到了字段timestamp
然后赋值给@timestamp加了上海时区
message: 2022-07-22T16:00:00.000+08:00
timestamp: 2022-07-22 16:00:00.000 ----给flink用,因为flink不支持解析T和0800
@timestamp:2022-07-22T08:00:00.000Z----存到es的时间,显示上隔8个小时,搜索用这个字段,搜索的结果用timestamp字段
日志汇搜logstash流程解析
conf-filter内容
filter { grok { match => { "message" => "%{TIMESTAMP_ISO8601:log_time}%{SPACE}%{LOGLEVEL:log_level}%{SPACE}(?<component>[a-zA-Z0-9]+)\.(?<segment>[a-zA-Z0-9]+)%{SPACE}\[(?<thread_name>[a-zA-Z0-9-]*)\]%{SPACE}\[(?<java_class>[a-zA-Z\.]+:\d+)\]%{SPACE}(<(?<trace_id>[a-z0-9]*))?,?((?<span_id>[a-z0-9]*)>)?%{SPACE}\[?(?<error_code>[a-zA-Z0-9]*)?\]?%{SPACE}%{SPACE}(?<log_data>.*)" } } }
解析结果示例
结论
解析出来的日志时间字段是log_time,原封不动的截取的字符值2022-07-22T16:00:00.000+08:00
带有+08:00时区标志,存入es后的显示时间和日志本身的时间一致,查询条件和搜索结果都不需要处理
@timestamp是采集时间,没有实际业务含义
Es测试动态mapping下不同日期格式的测试方案
1、 创建2个索引,均为动态mapping,时间字段均为@timestamp
(1)yanbiao_test_01(动态不指定date)
put http://10.192.77.152:9200/yanbiao_test_01
{
"mappings":
{"dynamic": "true" }
}
(2)yanbiao_test_02(动态mapping且指定date)
put http://10.192.77.152:9200/yanbiao_test_02
{ "mappings": { "dynamic": "true", "properties": { "@timestamp":{ "type":"date", "format": "yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS’Z’" } } } }
2、向2个索引分别插入值为
2022-07-22T16:00:00.000+08:00
2022-07-22T16:00:00.000Z
2022-07-22 16:00:00.000
插入第一个索引
Post http://10.192.77.152:9200/yanbiao_test_01/_doc/
{"@timestamp":"2022-07-22T16:00:00.000+08:00"} --成功
{"@timestamp":"2022-07-22T16:00:00.000Z"} --成功
{"@timestamp":"2022-07-22 16:00:00.000"} ---失败,印证了上述结论,这种格式需要事先指定格式
插入第二个索引
Post http://10.192.77.152:9200/yanbiao_test_02/_doc/
{"@timestamp":"2022-07-22T16:00:00.000+08:00"} ------失败,指定的格式中不支持这种
{"@timestamp":"2022-07-22T16:00:00.000Z"} ------成功
{"@timestamp":"2022-07-22 16:00:00.000"} ------成功
观察插入结果,主要观察插入是否成功,插入后在head中查看时是否减去了8小时
结论:插入后都没有8小时误差
3、针对2个索引做时间范围查询,确定查询条件是否需要增加时区或者加8小时才能查到
观察查询结果
注意:查询条件只需要转为时间戳查询,不需要处理8小时的问题
Logstash关于时间字段的测试方案
bin/logstash -f yanbiao/test_date_es.conf --path.data=/opt/test/logstash-7.16.2/data/test --path.logs=/opt/test/logstash-7.16.2/logs/test
用这种方式:
grok { match => { "message" => "%{TIMESTAMP_ISO8601:log_time}" } } date { match => ["log_time","ISO8601"] target => "@timestamp" # timezone => "Asia/Shanghai" }
查询用@timestamp,生命周期用@timestamp,这个显示时间会-8小时
展示用log_time(message解析的时间)
@timestamp必须保留,因为后面生命周期可能会用
如果不处理,默认是采集日志时的处理时间,所以必须覆盖掉
几种有问题的思路和结果
grok { match => { "message" => "%{TIMESTAMP_ISO8601:@timestamp}" } }
上面这种写法,解析后无法覆盖@timestamp
ruby { code =>"event.set('@timestamp', LogStash::Timestamp.at(event.get('@timestamp').time.localtime + 8*60*60))" }
上面这种写法,@timestamp看上去是加了8小时,和正常时间一致,但是查询的时候需要将查询条件也加8小时
mutate { rename => { "timestamp" => "@timestamp" } }
这种写法会报错,@timestamp是系统自带的特殊的字段名,不支持rename插件重命名为这个字段名