写在之前
Prometheus 如果想报警的话,发出的邮件或者企业微信时,上面的时间是比中国东八区晚了8个小时,刚开始想通过把本地 /usr/share/zoneinfo/Asia/Shanghai 通过volume的方式挂载 到容器内的 /etc/localtime,尝试之后,发现不行。那么如何解决这个问题呢?其实有很多种解决方案,可以写一个插件,通过 webhook 的方式调用插件接口,把警报内容发给插件,由插件去把时间处理一下,还有一种方式,查看源码,为什么不能使用本地 /etc/localtime时区,找到原因,修改源码,重新编译,这次总结一下接下从源码的角度来分析下这个问题。
原因确认并修改
源码下载链接:https://codeload.github.com/prometheus/prometheus/tar.gz/v2.17.1,下载源码,然后解压。
~/prometheus-2.17.1/cmd/prometheus/main.go 是Prometheus 入口启动文件,因为我们发送的报警都会记录在日志文件中,只需要查找到日志是如何记录的,就可以找到问题的根因,通过 Prometheus 入口文件,发现使用的第三方库promlog.New(),如下:
日志修改
通过查看github.com/prometheus/common/promlog 官方库,发现默认使用的是UTC时区,通过查看官网说使用UTC是为了防止夏令时误差,所以我们只需要重写下这个 logger 让其使用本地时区即可,这里需要注意一点,不修改的话,不会影响 Prometheus WebUI 和 Grafana 的效果,但是收邮件或者微信就有点别扭,总是需要加上8个小时,重写 logger 如下:
var logger log.Logger
if os.Getenv("LocalTZ") != "" {
logger = func(config *promlog.Config) log.Logger {
var (
l log.Logger
le level.Option
)
if config.Format.String() == "logfmt" {
l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
} else {
l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
}
switch config.Level.String() {
case "debug":
le = level.AllowDebug()
case "info":
le = level.AllowInfo()
case "warn":
le = level.AllowWarn()
case "error":
le = level.AllowError()
}
l = level.NewFilter(l, le)
l = log.With(l, "ts", log.TimestampFormat(
func() time.Time { return time.Now().Local() },
"2006-01-02T15:04:05.000Z07:00",
), "caller", log.DefaultCaller)
return l
}(&cfg.promlogConfig)
} else {
logger = promlog.New(&cfg.promlogConfig)
}
我们使用 time.Now().Local(),如果不设置环境变量 LocalTZ,还是使用默认的 UTC 时区即可。
Prometheus WebUI 修改
配置文件路径/prometheus-2.17.1/web/ui/static/js/graph/index.js,主要是修改这个index.js文件。
# 被修改行1
var date = '' + new Date(x * 1000).toUTCString() + '';
# 修改为:
var date = '' + new Date(x * 1000).toString() + '';
# 被修改行2
return self.endDate.data('DateTimePicker').date();
# 修改为:
return self.endDate.data('DateTimePicker').getLocalDate().getTime();