Logstash中建ES索引的时区问题
最近工作中遇到一个Logstash中建ES索引的时区问题,对数据统计造成了一定的影响。
logstash.conf文件(简化了业务代码):
input{ ... } filter{ date { match => ["access_time", "yyyy/MM/dd HH:mm:ss Z"] target => "@timestamp" } } output { #stdout { codec => rubydebug } elasticsearch { hosts => [...] index => "log-%{+YYYY.MM.dd}" } }
问题描述:
我们把日志中的时间存进变量access_time字段,按天建立ES中的索引统计日志,预想情况是例如access_time为2020.12.15的日志都存在ES的log-2020.12.15索引中。
我们后续采用Grafana查询ES,比如根据@timestamp查某个时间范围内的数据。由于logstash会默认把@timestamp转换成UTC时间(格林威治标准时间),我们专门进行了时间转换,把日志中的access_time字段(北京时间)通过match的格式匹配转换,使得@timestamp字段也是北京时间。此时日志中的access_time与logstash中的@timestamp完全一致,建ES索引时,%{+YYYY.MM.dd}会根据@timestamp字段的值取出年月日。
看似很顺利,但问题出现了。Elasticsearch 内部,对时间类型字段,是统一采用 UTC 时间,存成 long 长整形数据的!对日志统一采用 UTC 时间存储,是国际安全/运维界的一个通识——欧美公司的服务器普遍广泛分布在多个时区里——不像中国,地域横跨五个时区却只用北京时间。因此ES在建索引时会把@timestamp这个时间类型字段又转成UTC时间(-8小时)。
此时数据就会出现统计偏差,access_time为 2020/12/15 00:00 — 2020/12/15 8:00 之间的日志数据,本该存放在log-2020.12.15索引下,但由于时区转换问题,ES读取的@timestamp被减了8小时,为 2020/12/14 16:00 — 2020/12/15 00:00,导致这些日志数据会被统计到log-2020.12.14索引下。
而log-2020.12.15索引下统计的日志,实际上它们的access_time字段是在 2020/12/15 8:00 — 2020/12/16 8:00
图1
图2
如图1所示,对于页面查看,ELK 的解决方案是在 Kibana 上,读取浏览器的当前时区,然后在页面上转换时间内容的显示。
如图2所示,我们可以看到图2中Kibana已经自动把@timestamp转成了北京时间。这是一条access_time为 2020/12/15 06:00:00的日志,@timesstamp与access_time一致,但却是统计在log-2020.12.14索引中。
解决方法:
其实Kibana上看到的@timestamp已经是正确的了(与日志中的access_time一致),我们只需要解决索引的问题。
由于@timestamp字段是正确的,我们不改变该字段,而是用一个变量index_day代替@timestamp去建立索引。
这个变量中存储的时间是@timestamp+8小时,这样ES对于这个时间变量index_day就会-8小时,此时index_day在ES中存储的值就是北京时间,相当于建立索引时以北京时间为依据,也就对上了。
input{ ... } filter{ date { match => ["access_time", "yyyy/MM/dd HH:mm:ss Z"] target => "@timestamp" } ruby{ code => "event.set('index_day', (event.get('@timestamp').time.localtime + 8*60*60).strftime('%Y.%m.%d'))" } } output { #stdout { codec => rubydebug } elasticsearch { hosts => [...]
index => "log-%{index_day}" #index => "log-%{+YYYY.MM.dd}" } }
如果想及时知道创建的索引名字是否正确,可以先使用strftime('%Y.%m.%d.%H')替换strftime('%Y.%m.%d')进行测试。此时ES中索引的名字会显示小时,可以根据小时是否为北京时间判断是否更改成功。