如何更改 Python 日志记录中的时区?

Posted

技术标签:

【中文标题】如何更改 Python 日志记录中的时区?【英文标题】:How to Change the time zone in Python logging? 【发布时间】:2015-11-30 20:59:28 【问题描述】:

我想更改日志文件中的时间戳,使其反映我当前的时区,以便我可以更快地调试错误,

我可以更改日志文件中的时区吗?

目前我的配置是:

logging.basicConfig(filename='audit.log',
                filemode='w',
                level=logging.INFO,
                format='%(asctime)s %(message)s',
                datefmt='%m/%d/%Y %I:%M:%S %p')

【问题讨论】:

日志有各种格式。您可能需要发布样本。也就是说,如果您的意思是更改现有日志文件的时区。 它当前输出时间的时区是什么?运行代码的计算机上是否正确设置了时区?因为根据logging page默认使用time.localtime() 它位于阿拉斯加时区 UTC-09:00。我无法更改系统时区,因为还有其他依赖于时间的应用程序。 没有改变时区的问题。所以记录器以 UTC-09 输出时间。你想要那个时区(你的时区是什么)?您能否发送以下在 Python 中执行的语句的输出:print time.tzname, time.timezone, time.localtime(), time.gmtime()(不要忘记之前import time)。 正如@wyrmwood 所说,“根据应用程序,登录本地时区可能会产生歧义或每年至少两次造成混淆,其中会跳过凌晨 2 点或重复凌晨 1 点,可能还有其他时间。”我想在这里重复他的信息,因为在我看来这是错误的方式,而且根据我的经验,当我们总是登录 UTC 时,我们都会做得更好。也许您可以通过其他方式调整您的工具来帮助您解决问题? 【参考方案1】:

如何记录时区

%Z 来自strftime 格式

窗户

>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:29:54 PM Mountain Daylight Time test

Linux

>>> import logging
>>> logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%m/%d/%Y %I:%M:%S %p %Z")
>>> logging.error('test')
11/03/2017 02:30:50 PM MDT test

如果问题是

我如何登录与服务器本地时间不同的时区?

部分答案是logging.Formatter.converter,但是,您必须了解幼稚和有意识的datetime 对象。除非您想编写自己的时区模块,否则我强烈建议您使用 pytz 库 (pip install pytz)。 Python 3 包括一个 UTC 和 UTC 偏移时区,但是对于夏令时或其他偏移,您必须实施一些规则,所以我建议使用 pytz 库,即使对于 python 3 也是如此。

例如,

>>> import datetime
>>> utc_now = datetime.datetime.utcnow()
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'
>>> utc_now.tzinfo
(None)

如果我对这个 datetime 对象应用时区,时间不会改变(或者会为 ValueError)。

>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-21T02:30:09.422638-06:00'
>>> utc_now.isoformat()
'2019-05-21T02:30:09.422638'

但是,如果相反,我会这样做

>>> import pytz
>>> utc_now = datetime.datetime.now(tz=pytz.timezone('UTC'))
>>> utc_now.tzinfo
<UTC>

现在我们可以在我们希望的任何时区创建一个正确翻译的datetime 对象

>>> mst_now = utc_now.astimezone(pytz.timezone('America/Denver'))
>>> mst_now.isoformat()
'2019-05-20T20:31:44.913939-06:00'

啊哈!现在将其应用于日志记录模块。

Epoch 时间戳到带有时区的字符串表示

LogRecord.created 属性设置为time 模块创建LogRecord 的时间(由time.time() 返回)。这将返回一个时间戳 (seconds since the epoch)。您可以自己翻译到给定的时区,但我还是建议pytz,通过覆盖转换器。

import datetime
import logging
import pytz

class Formatter(logging.Formatter):
    """override logging.Formatter to use an aware datetime object"""
    def converter(self, timestamp):
        dt = datetime.datetime.fromtimestamp(timestamp)
        tzinfo = pytz.timezone('America/Denver')
        return tzinfo.localize(dt)
        
    def formatTime(self, record, datefmt=None):
        dt = self.converter(record.created)
        if datefmt:
            s = dt.strftime(datefmt)
        else:
            try:
                s = dt.isoformat(timespec='milliseconds')
            except TypeError:
                s = dt.isoformat()
        return s

Python 3.5、2.7

>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:25:10.758782-06:00 test

Python 3.7

>>> logger = logging.root
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(Formatter("%(asctime)s %(message)s"))
>>> logger.addHandler(handler)
>>> logger.setLevel(logging.DEBUG)
>>> logger.debug('test')
2019-05-20T22:29:21.678-06:00 test

America/Anchorage 替换America/Denver 来代替pytz 定义的posix 时区

>>> next(_ for _ in pytz.common_timezones if 'Alaska' in _)
'US/Alaska'

US/Alaska is deprecated

>>> [_ for _ in pytz.all_timezones if 'Anchorage' in _]
['America/Anchorage']

本地

如果您在寻找如何记录本地时区时遇到此问题和答案,则不要硬编码时区,而是获取 tzlocal (pip install tzlocal) 并替换

        tzinfo = pytz.timezone('America/Denver')

        tzinfo = tzlocal.get_localzone()

现在它可以在运行脚本的任何服务器上运行,服务器上的时区。

不记录 UTC 时的警告

我应该补充一点,根据应用程序,登录本地时区可能会产生歧义或至少每年两次造成混淆,其中跳过凌晨 2 点或重复凌晨 1 点,可能还有其他时间。

【讨论】:

我不明白。这将如何改变时区?它只是打印出系统的时区。如果系统的时区设置为 UTC,这很有可能,它只会打印出“...UTC test”(或任何 UTC 字符串)。但这可能与我正在调试的时区不同(即调试远程服务器时)。 @25mhz 在评论中声明 “它在阿拉斯加时区 UTC-09:00。我无法更改系统时区,因为还有其他依赖于时间的应用程序。” 如果我没记错的话,您的方法将始终打印出该系统的时区,即执行包含您的代码的脚本的位置。然后系统的时区将始终显示为 AKST(也可能是 AKDT)。这不是他想要的,他希望能够调整服务器上的一些代码,以便日志显示他所在的时区,而不影响除记录器输出之外的任何其他内容。我就是这么看的。 一年一次。第二次跳过小时而不会产生歧义。 @Wyrmwood OMG ...感谢您提供如此详细的解释!这正是我需要的!【参考方案2】:
#!/usr/bin/env python
from datetime import datetime
import logging
import time

from pytz import timezone, utc


def main():
    logging.basicConfig(format="%(asctime)s %(message)s",
                        datefmt="%Y-%m-%d %H:%M:%S")
    logger = logging.getLogger(__name__)
    logger.error("default")

    logging.Formatter.converter = time.localtime
    logger.error("localtime")

    logging.Formatter.converter = time.gmtime
    logger.error("gmtime")

    def customTime(*args):
        utc_dt = utc.localize(datetime.utcnow())
        my_tz = timezone("US/Eastern")
        converted = utc_dt.astimezone(my_tz)
        return converted.timetuple()

    logging.Formatter.converter = customTime
    logger.error("customTime")

    # to find the string code for your desired tz...
    # print(pytz.all_timezones)
    # print(pytz.common_timezones)


if __name__ == "__main__":
    main()
表面上,pytz 包是在 Python 中转换时区的好方法。所以我们从datetime 开始,转换,然后得到(不可变的)time_tuple 以匹配time 方法的返回类型 这个答案推荐设置logging.Formatter.converter函数:(Python logging: How to set time to GMT)。 取消对末尾行的注释,找到您最喜欢的 TZ 代码

【讨论】:

这实际上并没有记录时区。【参考方案3】:
#!/usr/bin/python

from datetime import datetime
from pytz import timezone
import logging

def timetz(*args):
    return datetime.now(tz).timetuple()

tz = timezone('Asia/Shanghai') # UTC, Asia/Shanghai, Europe/Berlin

logging.Formatter.converter = timetz

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

logging.info('Timezone: ' + str(tz))

使用 pytz 定义相对于 UTC 的时区。 基于示例:secsilm

【讨论】:

【参考方案4】:

只需将此 pythonic 行添加到您的代码中(使用 pytz 和日期时间):

from pytz import timezone
from datetime import datetime

logging.Formatter.converter = lambda *args: datetime.now(tz=timezone('tz string name')).timetuple()

# quoting Ryan J McCall: to find the string name for your desired timezone...
# print(pytz.all_timezones)
# or print(pytz.common_timezones)

【讨论】:

【参考方案5】:

如果您想使用日志记录配置功能,另一种解决方案:

import pytz
import logging
import logging.config
from datetime import datetime

tz = pytz.timezone('Asia/Tokyo')

class TokyoFormatter(logging.Formatter):
    converter = lambda *args: datetime.now(tz).timetuple()

LOGGING = 
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': 
        'Tokyo': 
            '()': TokyoFormatter,
            'format': '%(asctime)s %(levelname)s: %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        ,
    ,
    'handlers': 
        'console': 
            'class': 'logging.StreamHandler',
            'formatter': 'Tokyo'
        ,
    ,
    'loggers': 
        'foo': 
            'handlers': ['console'],
            'level': 'INFO'
        ,
    


logging.config.dictConfig(LOGGING)
logger = logging.getLogger('foo')
logger.info('Just a test.')

定义日志格式化程序,例如“TokyoFormatter”。它有一个属性“转换器”,完成转换时区的工作。 更多详情请参考Customizing handlers with dictConfig()。

【讨论】:

【参考方案6】:
import logging, time
from datetime import datetime, timedelta
logger = logging.getLogger(__name__)
converter = lambda x, y: (datetime.utcnow() - timedelta(
    hours=7 if time.localtime().tm_isdst else 6)
).timetuple()
logging.Formatter.converter = converter

编辑为 Elias 指出原始答案未检查 DST。

【讨论】:

这不涉及夏令时。【参考方案7】:

如果您知道您的utc offset,您可以定义一个函数来校正时间,然后将其传递给logging.Formatter.converter

比如你想把时间转换成UTC+8时区,那么:

import logging
import datetime


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


logging.Formatter.converter = beijing

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s",
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

只需根据您的情况更改datetime.timedelta(hours=8) 的营业时间即可。

参考:https://alanlee.fun/2019/01/06/how-to-change-logging-date-timezone/

【讨论】:

以上是关于如何更改 Python 日志记录中的时区?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Heroku 使用服务器时间而不是 Rails 时区进行日志记录?

关键云代工厂的时区变化

如何从 Python 中的请求模块中完全删除任何日志记录

如何使用日志记录 Python 模块写入文件?

每当有人尝试从 Python 中的特定文件夹复制任何文件时如何记录日志

在 python 项目中如何记录日志