【问题标题】:Python logging: use milliseconds in time formatPython 日志记录:使用毫秒的时间格式
【发布时间】:2011-09-11 13:19:37
【问题描述】:

默认情况下logging.Formatter('%(asctime)s') 使用以下格式打印:

2011-06-09 10:54:40,638

其中 638 是毫秒。我需要把逗号改成点:

2011-06-09 10:54:40.638

格式化我可以使用的时间:

logging.Formatter(fmt='%(asctime)s',datestr=date_format_str)

但是documentation 没有指定如何格式化毫秒。我发现this SO question 谈论微秒,但是a)我更喜欢毫秒,b)由于%f,以下不适用于Python 2.6(我正在研究):

logging.Formatter(fmt='%(asctime)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')

【问题讨论】:

  • 也许改变语言环境会有所帮助?
  • @pajton - 在以下链接中显示“asctime() 不使用区域设置信息” - docs.python.org/library/time.html#time.asctime
  • %f 也不适用于 python 2.7.9 或 3.5.1
  • 这里的谈话很好。我来到这里是因为logging 声称其默认时间格式遵循 ISO 8601。它没有。它使用空格,而不是“T”来分隔小数秒的时间和逗号,而不是小数点。他们怎么会这么错?

标签: python logging time


【解决方案1】:

这也应该有效:

logging.Formatter(fmt='%(asctime)s.%(msecs)03d',datefmt='%Y-%m-%d,%H:%M:%S')

【讨论】:

  • 谢谢:以下是这些文档:docs.python.org/2/library/logging.html#logrecord-attributes docs.python.org/3/library/logging.html#logrecord-attributes .. 有没有办法仍然包含时区 (%z)? ... Python 日志 (, -> .) 中的 ISO8601 格式时间会很棒。
  • 这个解决方案是有缺陷的,因为如果你的 datefmt 中有 %z%Z,你希望它出现在毫秒之后,而不是之前。
  • 如果你使用的是 12 小时制时钟,它有 AMPM
  • @wim 这是一种解决方法,但是通过执行logging.Formatter.converter = time.gmtime 使用UTC 而不是本地时间,那么您不需要使用%z%Z。或者,您可以将 logging.Formatter 对象的 default_msec_format 属性更改为 %s,%03d%z 或 %s,%03d%Z
  • @wim 作为我之前评论的后续(无法再编辑...),这是我所做的:from time import gmtime - # Use UTC rather than local date/time - logging.Formatter.converter = gmtime - @ 987654337@
【解决方案2】:

请注意Craig McDaniel's solution 显然更好。


logging.Formatter 的formatTime 方法如下所示:

def formatTime(self, record, datefmt=None):
    ct = self.converter(record.created)
    if datefmt:
        s = time.strftime(datefmt, ct)
    else:
        t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
        s = "%s,%03d" % (t, record.msecs)
    return s

注意"%s,%03d" 中的逗号。这不能通过指定datefmt 来解决,因为cttime.struct_time,并且这些对象不记录毫秒。

如果我们更改ct 的定义以使其成为datetime 对象而不是struct_time,那么(至少对于现代版本的Python)我们可以调用ct.strftime,然后我们可以使用@987654332 @ 格式化微秒:

import logging
import datetime as dt

class MyFormatter(logging.Formatter):
    converter=dt.datetime.fromtimestamp
    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = ct.strftime(datefmt)
        else:
            t = ct.strftime("%Y-%m-%d %H:%M:%S")
            s = "%s,%03d" % (t, record.msecs)
        return s

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

console = logging.StreamHandler()
logger.addHandler(console)

formatter = MyFormatter(fmt='%(asctime)s %(message)s',datefmt='%Y-%m-%d,%H:%M:%S.%f')
console.setFormatter(formatter)

logger.debug('Jackdaws love my big sphinx of quartz.')
# 2011-06-09,07:12:36.553554 Jackdaws love my big sphinx of quartz.

或者,要获得毫秒,请将逗号更改为小数点,并省略 datefmt 参数:

class MyFormatter(logging.Formatter):
    converter=dt.datetime.fromtimestamp
    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        if datefmt:
            s = ct.strftime(datefmt)
        else:
            t = ct.strftime("%Y-%m-%d %H:%M:%S")
            s = "%s.%03d" % (t, record.msecs)
        return s

...
formatter = MyFormatter(fmt='%(asctime)s %(message)s')
...
logger.debug('Jackdaws love my big sphinx of quartz.')
# 2011-06-09 08:14:38.343 Jackdaws love my big sphinx of quartz.

【讨论】:

  • 所以 %f 实际上会给出微秒,而不是毫秒,对吧?
  • @Jonathan:哎呀,你是对的,%f 给出了微秒。我想获得毫秒的最简单方法是将逗号更改为小数点(参见上面的编辑)。
  • 我实际上认为这是最好的答案,因为它可以让您重新能够使用标准格式选项。我实际上想要微秒,这是唯一可以做到的选择!
【解决方案3】:

添加毫秒是更好的选择,谢谢。 这是我在 Blender 中使用 Python 3.5.3 进行的修改

import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s.%(msecs)03d %(levelname)s:\t%(message)s', datefmt='%Y-%m-%d %H:%M:%S')
log = logging.getLogger(__name__)
log.info("Logging Info")
log.debug("Logging Debug")

【讨论】:

  • 迄今为止最简单、最干净的选择。当您可以调用 logging.info(msg) 等时,不确定为什么要获取记录器,但格式正是我想要的。任何寻找所有可用属性的人都可以在这里查看:docs.python.org/3.6/library/logging.html#logrecord-attributes
  • 嗯,有趣的一点,感谢您的评论,这肯定是值得深思的。是的,我可能只是将它添加为那里发生的事情的教训,并确保它在那里,因为我已经问了很多事情,所以它不需要多次调用父级(通过'。')来获取它。如果您再次调用 .info 或 .debug,我可能会再次直接保存它们,因为您建议保存参考查找周期。 [让 info = logging.info]
  • @naphier,您正在获取记录器而不是“只是”调用logging.info(msg),因为您通常想利用 Python 的记录器名称层次结构。 (例如为某些包设置不同的详细级别或依赖%(levelname) 产生有用的值)。另见例如python3 -c 'import logging; logging.basicConfig(); logging.warning("foo"); logging.getLogger(__name__).warning("bar")'.
【解决方案4】:

我发现的最简单的方法是覆盖 default_msec_format:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format = '%s.%03d'

【讨论】:

  • 很有趣,谢谢。但这在 Python 2.7 中对我不起作用。对于某些 x 值,它可能仅适用于 Python 3.x。
  • @nealmcb 根据docs@nealmcb 这直到 Python 3.3 才可用
  • 确实很简单!不幸的是,它仅在使用默认时间格式(即datefmt=None)时才有效。当使用不同的日期格式时(例如,当您想以毫秒打印时间但没有数据时)此解决方案不适用。
【解决方案5】:

这里有许多过时、过于复杂和奇怪的答案。原因是文档不足,简单的解决方法就是使用basicConfig(),然后设置如下:

logging.basicConfig(datefmt='%Y-%m-%d %H:%M:%S', format='{asctime}.{msecs:0<3.0f} {name} {threadName} {levelname}: {message}', style='{')

这里的诀窍是您还必须设置 datefmt 参数,因为 默认 搞砸了它并且 不是 (当前)显示在how-to python docs 中。所以宁愿看here


另一种可能更简洁的方法是使用以下命令覆盖 default_msec_format 变量:

formatter = logging.Formatter('%(asctime)s')
formatter.default_msec_format = '%s.%03d'

但是,由于未知原因,这不起作用

PS。我正在使用 Python 3.8。

【讨论】:

  • 不确定您的解决方案是否有效。
  • @mikerodent 就像我说的,我使用并为我工作的第一个,在文档中找到的秒数并没有工作。因此,如果您“不确定”,您实际上在说什么?
  • 不打印时区,所以恐怕不是 ISO8601,但如果你只需要毫秒就可以了
【解决方案6】:

我想出了一个双线来让 Python 日志记录模块以 RFC 3339(符合 ISO 1801)格式输出时间戳,同时具有格式正确的毫秒和时区 并且 没有外部依赖: p>

import datetime
import logging

# Output timestamp, as the default format string does not include it
logging.basicConfig(format="%(asctime)s: level=%(levelname)s module=%(module)s msg=%(message)s")

# Produce RFC 3339 timestamps
logging.Formatter.formatTime = (lambda self, record, datefmt: datetime.datetime.fromtimestamp(record.created, datetime.timezone.utc).astimezone().isoformat())

例子:

>>> logging.getLogger().error("Hello, world!")
2021-06-03T13:20:49.417084+02:00: level=ERROR module=<stdin> msg=Hello, world!

或者,最后一行可以写成如下:

def formatTime_RFC3339(self, record, datefmt=None):
    return (
        datetime.datetime.fromtimestamp(record.created, datetime.timezone.utc)
        .astimezone()
        .isoformat()
    )

logging.Formatter.formatTime = formatTime_RFC3339

该方法也可以用于特定的格式化程序实例,而不是在类级别覆盖,在这种情况下,您需要从方法签名中删除 self

【讨论】:

    【解决方案7】:

    不需要datetime 模块并且不像其他一些解决方案那样有障碍的简单扩展是使用简单的字符串替换,如下所示:

    import logging
    import time
    
    class MyFormatter(logging.Formatter):
        def formatTime(self, record, datefmt=None):
            ct = self.converter(record.created)
            if datefmt:
                if "%F" in datefmt:
                    msec = "%03d" % record.msecs
                    datefmt = datefmt.replace("%F", msec)
                s = time.strftime(datefmt, ct)
            else:
                t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
                s = "%s,%03d" % (t, record.msecs)
            return s
    

    通过这种方式,您可以根据需要编写日期格式,甚至允许区域差异,通过使用%F 表示毫秒。例如:

    log = logging.getLogger(__name__)
    log.setLevel(logging.INFO)
    
    sh = logging.StreamHandler()
    log.addHandler(sh)
    
    fm = MyFormatter(fmt='%(asctime)s-%(levelname)s-%(message)s',datefmt='%H:%M:%S.%F')
    sh.setFormatter(fm)
    
    log.info("Foo, Bar, Baz")
    # 03:26:33.757-INFO-Foo, Bar, Baz
    

    【讨论】:

      【解决方案8】:

      在实例化Formatter 后,我通常设置formatter.converter = gmtime。因此,为了让@unutbu 的答案在这种情况下起作用,您需要:

      class MyFormatter(logging.Formatter):
          def formatTime(self, record, datefmt=None):
              ct = self.converter(record.created)
              if datefmt:
                  s = time.strftime(datefmt, ct)
              else:
                  t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
                  s = "%s.%03d" % (t, record.msecs)
              return s
      

      【讨论】:

        【解决方案9】:

        如果您使用arrow 或者如果您不介意使用箭头。您可以用 python 的时间格式替换箭头的时间格式。

        import logging
        
        from arrow.arrow import Arrow
        
        
        class ArrowTimeFormatter(logging.Formatter):
        
            def formatTime(self, record, datefmt=None):
                arrow_time = Arrow.fromtimestamp(record.created)
        
                if datefmt:
                    arrow_time = arrow_time.format(datefmt)
        
                return str(arrow_time)
        
        
        logger = logging.getLogger(__name__)
        
        default_handler = logging.StreamHandler()
        default_handler.setFormatter(ArrowTimeFormatter(
            fmt='%(asctime)s',
            datefmt='YYYY-MM-DD HH:mm:ss.SSS'
        ))
        
        logger.setLevel(logging.DEBUG)
        logger.addHandler(default_handler)
        

        现在您可以在datefmt 属性中使用所有arrow's time formatting

        【讨论】:

          【解决方案10】:

          如果您更喜欢使用style='{'fmt="{asctime}.{msecs:0&lt;3.0f}" 会将您的微秒填充到三个位置以保持一致性。

          【讨论】:

          • 这只是在logger.Formatter() 中使用时添加,nnn 格式(到%S 部分)。所以需要在basicConfig(datefmt=..., format=..., style='{')中使用。
          【解决方案11】:

          在消耗了我一些宝贵的时间之后,下面的 hack 对我有用。我刚刚在settings.py 中更新了我的格式化程序,并将datefmt 添加为%y/%b/%Y %H:%M:%S 并将毫秒附加到asctime,如下所示{asctime}.{msecs:0&lt;3.0f}

          例如:

              'formatters': {
                  'verbose': {
                      'format': '[{asctime}.{msecs:0<3.0f}] {levelname} [{threadName:s}] {module} → {message}',
                      'datefmt': "%y/%b/%Y %H:%M:%S",
                      'style': '{',
                  },
              }
          

          【讨论】:

            【解决方案12】:

            tl;dr 对于在这里寻找 ISO 格式日期的人们:

            不要使用类似 '%Y-%m-%d %H:%M:%S.%03d%z' 之类的东西,而是按照@unutbu 的指示创建自己的类。这是iso日期格式的一个:

            import logging
            from time import gmtime, strftime
            
            class ISOFormatter(logging.Formatter):
                def formatTime(self, record, datefmt=None):
                    t = strftime("%Y-%m-%dT%H:%M:%S", gmtime(record.created))
                    z = strftime("%z",gmtime(record.created))
                    s = "%s.%03d%s" % (t, record.msecs,z)        
                    return s
            
            logger = logging.getLogger(__name__)
            logger.setLevel(logging.DEBUG)
            
            console = logging.StreamHandler()
            logger.addHandler(console)
            
            formatter = ISOFormatter(fmt='%(asctime)s - %(module)s - %(levelname)s - %(message)s')
            console.setFormatter(formatter)
            
            logger.debug('Jackdaws love my big sphinx of quartz.')
            #2020-10-23T17:25:48.310-0800 - <stdin> - DEBUG - Jackdaws love my big sphinx of quartz.
            
            

            【讨论】:

            • %03d 打印日期,而不是毫秒。
            • %03d 是(是,见下文)在此上下文中毫秒的字符串格式修饰符,而不是此处所述的日期格式组件:docs.python.org/3/library/… 但是这是静音,因为此修饰符显然不再根据stackoverflow.com/questions/6290739/… 在 3.7 之后工作
            • 使用@unutbu 的起点更新了当前准确的答案
            【解决方案13】:

            截至目前,以下内容与 python 3 完美配合。

                     logging.basicConfig(level=logging.DEBUG,
                                 format='%(asctime)s %(levelname)-8s %(message)s',
                                 datefmt='%Y/%m/%d %H:%M:%S.%03d',
                                 filename=self.log_filepath,
                                 filemode='w')
            

            给出以下输出

            2020/01/11 18:51:19.011 信息

            【讨论】:

            • 这不起作用。 %d 正在打印日期。在您的示例中,日期打印为在其前面填充 0。
            猜你喜欢
            • 1970-01-01
            • 2014-10-05
            • 2013-12-15
            • 1970-01-01
            • 2011-03-14
            • 1970-01-01
            • 2012-01-12
            • 1970-01-01
            相关资源
            最近更新 更多