logging.Formatter 拖慢程序主因是每次日志输出时调用 time.localtime() 和字符串格式化,且即使被级别过滤仍执行格式化;应启用 lazy formatting、禁用高开销字段、改用结构化日志方案。

为什么 logging.Formatter 会拖慢程序?
Python 默认的 logging.Formatter 在每次日志输出时都会调用 time.localtime() 和字符串格式化(尤其是含 %(asctime)s 或 %(funcName)s),这些操作在高频打日志场景下会显著增加 CPU 开销。更隐蔽的问题是:如果日志级别被过滤掉(比如设为 WARNING 却调用了 logger.debug(...)),默认行为仍会执行消息格式化——即“先格式化再判断是否输出”,这完全浪费计算资源。
启用 lazy formatting:避免无意义的字符串拼接
核心解法是让格式化动作推迟到真正需要输出时才执行。标准库不直接支持,但可通过自定义 LogRecord 子类或第三方方案实现。最轻量的做法是改用 logging.Logger 的 log() 方法传参方式,而非提前拼接字符串:
- ❌ 错误写法(强制立刻格式化):
logger.info("user " + username + " logged in at " + str(time.time())) - ✅ 正确写法(延迟格式化):
logger.info("user %s logged in at %s", username, time.time())
这样即使该日志被级别过滤,% 格式化步骤根本不会触发。注意:必须用 % 占位符,f-string 或 .format() 都会在调用前求值,起不到懒加载效果。
禁用 asctime 和 funcName 等高开销字段
默认格式中 %(asctime)s 每次调用 time.time() + time.strftime();%(funcName)s 和 %(lineno)d 需要遍历栈帧,CPU 成本远高于其他字段。若非调试必需,应移除:
立即学习“Python免费学习笔记(深入)”;
- 去掉时间戳:用
%(created)f替代%(asctime)s(返回浮点秒数,不格式化) - 去掉函数名/行号:删除
%(funcName)s、%(lineno)d、%(module)s等字段 - 示例高效格式:
"%(levelname)s %(name)s %(created)f %(message)s"
实测在 10k 日志/秒 场景下,移除 asctime 和 funcName 可降低格式化耗时 40%~60%。
用 StructuredLogger 或第三方 formatter 替代原生方案
当需要结构化日志(如 JSON)又不想牺牲性能时,避免用 json.dumps() 在每次 format() 中调用。推荐两种方案:
- 使用
structlog:它默认延迟序列化,且支持绑定上下文(bind()),字段只在最终输出时计算 - 自定义
Formatter子类,在format()中缓存已计算字段(如把created转成字符串的操作提到filter()阶段)
特别注意:structlog 的 processors 链中,structlog.processors.TimeStamper(fmt="iso") 仍会触发格式化,应改为 fmt="unix_timestamp" 或直接用 float 类型。
真正难处理的是跨线程日志上下文(如 request_id),这类信息必须在记录时刻捕获,但容易因锁或对象拷贝引入新瓶颈——这里没有银弹,得按实际吞吐压测调整。


















