距离上次更新本文已经过去了 634 天,文章部分内容可能已经过时,请注意甄别

设置 logging 日志时间的时区

1. 问题

最近我的几个 python-bot 项目都放在了 replit 上面白嫖运行,于是遇到了一个很让人头疼的问题。replit 的终端采用的是 UTC 标准时间,比北京时间晚了 8h,而我之前的项目,全都是用 print 来打印信息的(当时还不会用 logging)

为了知道日志输出的时间,我在每一个 print 里面都调用了一个获取当前时间的函数

python
1
2
3
def getTime():
"""获取当前时间,格式为 `23-01-01 00:00:00`"""
return time.strftime("%y-%m-%d %H:%M:%S", time.localtime())

这个函数会返回系统的当前时间。对于 linux、windows 这些时区正确的机器而言,输出的都是北京时间,没有什么问题。

如果你的 linux 机器(特别是用 docker 安装的)时区有问题,可以参考这篇文章来解决: 点我

但是到 replit 上面就不一样了,即便你在.replit 文件中添加了时区的环境变量,其依旧无法让 localtime 返回北京时间

plaintext
1
TZ = "Asia/Shanghai"

配置了 TZ 环境变量后,shell 中输入 date 查看当前时间,依旧晚 8h,并非北京时间。

image-20230324202422995

我已经尝试过了,replit 这个并不能像 linux 系统修改时区 一样修改系统时间。

我们就只能从 python 的代码下手来解决这个问题了。

2. 解决

首先是 getTime 函数如何强制返回北京时间,我们需要将 time 改成使用 datetime

python
1
2
3
4
5
6
7
from datetime import datetime
from zoneinfo import ZoneInfo # 在Py3.9以后是标准库

def getTime():
"""获取当前时间,格式为 `23-01-01 00:00:00`"""
a = datetime.now(ZoneInfo('Asia/Shanghai')) # 返回北京时间
return a.strftime('%y-%m-%d %H:%M:%S')

针对 logging 模块,也需要进行特殊处理

参考 https://zhuanlan.zhihu.com/p/304672864

查阅文档,得知 logging 模块中 asctime 的时间使用的是 time.localtime() 返回的时间

文档:library/logginglibrary/time

time.localtime() 又是使用的 time.time() 返回的时间,默认情况下, time.time() 返回的是 UTC 时间戳,即从 1970-1-1 00:00:00 到现在的秒数,由于时区不对,时间戳没有转成中国所在的 UTC+8 时区的时间字符串。

经过 stackoverflow 上这篇回答的提醒,可以设定 logging.Formatter.converter 来转换可读时间,但是回答里是转换成 GMT 时间,我们需要自己重写一个函数来加上 UTC 偏移,返回正确的东八区时间,传给 logging.Formatter.converter

修改 logging.Formatter.converter

python
1
2
3
4
5
6
7
8
9
10
11
12
13
import logging
import datetime

def beijing(sec, what):
beijing_time = datetime.datetime.now() + datetime.timedelta(hours=8)
return beijing_time.timetuple()

logging.Formatter.converter = beijing

# 只打印info以上的日志(debug低于info)
logging.basicConfig(level=logging.INFO,
format="[%(asctime)s] %(levelname)s:%(filename)s:%(funcName)s:%(lineno)d | %(message)s",
datefmt="%y-%m-%d %H:%M:%S")

还可以使用如下办法

python
1
2
3
4
5
6
7
8
9
import logging
from datetime import datetime
from zoneinfo import ZoneInfo # py3.9后是系统库

def beijing(sec, what):
beijing_time = datetime.now(ZoneInfo('Asia/Shanghai')) # 返回北京时间
return beijing_time.timetuple()
# 日志时间改为北京时间
logging.Formatter.converter = beijing

两种办法都可以的!其中第二个办法需要 python 3.9 + 才能支持

3. 验证

可以看到,修改之前,log 输出的时间离正确的时间差 8h

plaintext
1
[23-03-24 10:57:07]

image-20230324203308707

修改了之后,输出的时间就对了!已经是东八区的时间了。

plaintext
1
[23-03-24 20:13:04]

image-20230324203401212

不过,修改这个也需要看你的项目面向的对象是谁。因为我写的都是 kook 平台的机器人,kook 作为国内平台,压根没有几个歪果仁用,所以直接修改,将程序里面所有需要涉及到可读时间的都指定为北京时间。

如果你的项目是 discord 平台的机器人,那最好还是保留原状吧!

需要注意的是,时区的设置影响的是可读时间,即 23-03-24 20:14:48 这样的时间字符串;和时间戳本身的秒数并没有关系。