Python日志:使用时间格式的毫秒

默认情况下,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)

但是文档没有指定如何格式化毫秒。我发现这个SO问题谈论微秒,但是a)我更喜欢毫秒和b)由于%f,以下在Python 2.6(我正在工作)上不起作用:

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

请注意Craig McDaniel的解决方案显然更好。


日志记录。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,然后我们可以使用%f格式化微秒:

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.

这也可以工作:

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

实例化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

我找到的最简单的方法是覆盖default_msec_format:

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

不需要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

如果你正在使用箭头或者如果你不介意使用arrow。你可以用python的时间格式代替arrow的时间格式。

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属性中使用所有的阿罗时间格式

添加msecs是更好的选择,谢谢。 以下是我在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")

到目前为止,下面的代码与python3完全兼容。

         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信息

tl;dr给那些在这里寻找ISO格式日期的人:

而不是使用'%Y-%m-%d %H:% m:%S。%03d%z',按@unutbu指示创建自己的类。下面是iso date格式:

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.


如果你更喜欢使用style='{'fmt="{asctime}.{msecs:0<3.0f}"会将你的微秒0-pad到三个位置以保持一致性。

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

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

这里的技巧是你还必须设置datefmt参数,因为默认的把它弄乱了,并且是(当前)显示在python文档指南中的东西。所以宁愿看在这里


另一种可能更干净的方法是用以下方法重写default_msec_format变量:

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

然而,由于未知的原因,没有工作

PS.我使用的是Python 3.8。

我找到了一个双行程序,让Python日志记录模块输出RFC 3339 (ISO 1801兼容)格式的时间戳,具有正确格式化的毫秒和没有外部依赖的时区而且:

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=None: 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

在消耗了我宝贵的时间之后,下面的方法对我有用。我刚刚在settings.py中更新了我的格式化器,并将datefmt添加为%y/%b/%Y %H:%M:%S,并像这样将毫秒追加到asctime

例句:

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

使用聪明的回答作为时区和选择答案,你可以用你想要的格式构造毫秒和时区:

import logging
import time


if __name__ == "__main__":
tz = time.strftime('%z')
logging.basicConfig(
format=(
"%(asctime)s.%(msecs)03d" + tz + " %(levelname)s "
"%(pathname)s:%(lineno)d[%(threadName)s]: %(message)s"
),
level=logging.DEBUG,
datefmt="%Y-%m-%dT%H:%M:%S",
)
logging.info("log example")

就我个人而言,我喜欢以UTC格式保存所有日志,但在日志中显式地将其作为没有时区的datetime,这在多区域应用程序中是没有意义的:

    logging.Formatter.converter = time.gmtime
logging.basicConfig(
format=(
"%(asctime)s.%(msecs)03d+0000 %(levelname)s "
"%(pathname)s:%(lineno)d[%(threadName)s]: %(message)s"
),
level=logging.DEBUG,
datefmt="%Y-%m-%dT%H:%M:%S",
)