在Python标准库中,logging模块是记录运行时信息的常用工具。很多初学者在配置Logger时,希望通过datefmt参数把日志时间精确到毫秒甚至微秒,但实际输出往往只到秒。这并非Bug,而是由logging底层对时间格式的处理方式决定的。理解其中的机制,才能写出符合精度要求的日志格式。

datefmt参数的底层限制
logging.Handler在格式化日志记录时,会调用Formatter对象。Formatter的datefmt参数最终被传递给time.strftime函数,用于把struct_time转换为字符串。Python的time.strftime遵循C语言标准库的传统格式定义,它提供的格式符如%Y、%m、%d、%H、%M、%S都只精确到秒,并没有原生的毫秒或微秒格式符。
这意味着,即使你在datefmt中写了类似%Y-%m-%d %H:%M:%S.%f的内容,strftime也无法识别%f,通常会原样输出字母f或者报错。logging模块并不会在datefmt阶段自动注入毫秒数据,它只是单纯地做一次strftime调用。因此,依赖datefmt显示亚秒级精度从设计上就走不通。
使用format中的msecs显示毫秒
logging在内部生成LogRecord时,除了asctime字段,还会计算millisecond级的msecs属性。我们可以在format字符串中直接使用%(msecs)03d来获取毫秒部分,而不通过datefmt控制。这样时间戳的日期部分仍由datefmt决定,毫秒部分由format补充。
下面是一段可用的基础配置代码,注意在format里拼接了msecs,datefmt只负责到秒:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s.%(msecs)03d %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logging.info('这条日志带毫秒')
这种写法的优点是简单直观,不需要自定义类。缺点是datefmt和毫秒被拆成两处维护,如果后期想调整日期顺序会比较麻烦。另外msecs只提供三位毫秒,不包含微秒。
自定义Formatter重写formatTime
如果需要更灵活的时间精度,比如微秒,或者希望整个时间格式统一在一个方法里控制,可以继承logging.Formatter并重写formatTime。在该方法中,我们可以使用datetime模块拿到包含微秒的时间对象,然后自由格式化。
示例代码如下,使用datetime.now()生成带微秒的字符串,并替换默认实现:
import logging
from datetime import datetime
class MicrosecondFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
# 使用record创建的datetime,包含微秒
dt = datetime.fromtimestamp(record.created)
if datefmt:
# 若传入datefmt则部分兼容,但这里直接忽略以统一格式
return dt.strftime('%Y-%m-%d %H:%M:%S') + '.%03d' % (dt.microsecond // 1000)
return dt.strftime('%Y-%m-%d %H:%M:%S.%f')
fmt = MicrosecondFormatter('%(asctime)s %(levelname)s %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(fmt)
logger = logging.getLogger('demo')
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.info('带微秒的日志输出')
这种方式的优势在于时间逻辑完全可控,你能输出任意精度,也能兼容ISO8601等标准。代价是代码量稍多,且需理解record.created是Unix时间戳浮点数,其小数部分即为亚秒精度。
常见误区与对比
有一种误解是:在datefmt里写%f就能拿到微秒,因为datetime.strftime支持%f。但logging的datefmt走的是time.strftime,不是datetime.strftime,两者后台不同。下面用表格列出关键差异:
| 方式 | 底层函数 | 毫秒/微秒支持 | 适用场景 |
|---|---|---|---|
| datefmt参数 | time.strftime | 不支持 | 仅秒级日期时间 |
| format中%(msecs)d | LogRecord属性 | 毫秒三位 | 快速加毫秒 |
| 重写formatTime | datetime.strftime | 微秒可选 | 高精度定制 |
从表中可以看出,如果只求稳定且足够用的毫秒级,用msecs最省事;若做性能分析或分布式追踪需要微秒,则应自定义Formatter。切忌在datefmt上反复试错,那只会浪费排查时间。
实践建议
在生产项目中,建议把日志格式定义为配置项。若使用字典配置logging,同样遵循上述规则:format字符串里写%(msecs)03d,datefmt不写亚秒符。对于需要JSON日志的场景,可在formatTime里返回ISO格式字符串,再由序列化逻辑处理。
另外注意,多进程写日志时,时间精度不会影响顺序,但毫秒能帮助定位同一秒内的并发行为。因此合理暴露毫秒,对后期排错价值明显。掌握datefmt的边界,才能把logging用得既规范又高效。