Python logging 不打印/重复打印/中文乱码?9 个高频坑症状→原因→修法对照表
先给结论logging出问题的九成原因是搞错了这三件事① logger 和 handler 是两级过滤都要过②basicConfig()只在 root 没配置过时生效③ handler 会重复叠加而 logger 默认还会向上传到 root。本文把最常见的 9 个坑做成症状 → 原因 → 修法对照表每一条都配可复现的最小代码。—## 一、先记住这张对照表排错时按症状查| 症状 | 真正的原因 | 修法 ||—|—|—||info()一点都不输出 | root logger 默认级别是WARNING|logging.basicConfig(levellogging.INFO)||basicConfig设了还是不输出 |之前已经有人配置过 root导库时触发 | 显式给自己的 logger 加 handler || 一条日志打印2 次| 既给 logger 加了 handler又传播到 root|logger.propagate False|| 一条日志打印3 次以上| handler 被重复添加模块级/函数级各加一次 | 加之前先判断if not logger.handlers|| 文件里中文是乱码|FileHandler默认用系统编码Windows 是 GBK | 显式encodingutf-8|| 文件里啥都没有 | 没flush进程被强杀 |handler.flush()或用RotatingFileHandler|| 时间字段是UTC|asctime用time.localtime的时区 | 自定义 formatter 的converter|| 多进程写日志丢行/串行错乱| 多个进程写同一个文件句柄 | 用ConcurrentRotatingFileHandler或队列 || 在 uvicorn/gunicorn 里失效| 框架接管了 root logger | 用dictConfig或框架提供的配置 |—## 二、坑 1info()不输出级别问题pythonimport logginglogging.info(看不到我) # 什么都不打印logging.warning(看得到我) # 只有这个会打印原因root logger 的默认级别是WARNINGINFO/DEBUG直接被拦掉。pythonimport logginglogging.basicConfig(levellogging.INFO, format%(levelname)s %(message)s)logging.info(这次能看到了)关键点级别判断发生在产生日志的第一关logger 自身级别随后还有 handler 的级别再过滤一次。—## 三、坑 2basicConfig设了却没生效pythonimport loggingimport requests # 很多库在 import 时就碰过 logginglogging.basicConfig(levellogging.INFO) # 无效root 已有 handler原因basicConfig()内部有一句“如果 root 已经有 handler就直接 return”。第三方库只要import时调过一次你的basicConfig就成了空操作。正确做法别依赖 basicConfig显式建自己的 logger。pythonimport logginglogger logging.getLogger(myapp) # 用模块名/应用名不要用 rootlogger.setLevel(logging.INFO)if not logger.handlers: # 关键防止重复添加 h logging.StreamHandler() h.setFormatter(logging.Formatter(%(asctime)s %(levelname)s %(message)s)) logger.addHandler(h)—## 四、坑 3一条日志打印两次propagatepythonlogger logging.getLogger(myapp)logger.addHandler(logging.StreamHandler())logging.basicConfig(levellogging.INFO) # root 也有 handlerlogger.info(hello)原因logger 的propagate默认为True日志传给自己的 handler 后还会向上传给 rootroot 的 handler 再打一遍。pythonlogger.propagate False # 一刀切不再往上传判断标准如果你的 logger 自己配了 handler就把propagate关掉。—## 五、坑 4打印 N 次handler 重复叠加pythondef get_logger(): lg logging.getLogger(myapp) lg.addHandler(logging.StreamHandler()) # 每次调用都加一个 return lgget_logger(); get_logger(); get_logger().info(hi) # 打印 3 次原因getLogger返回的是同一个单例但addHandler会无脑追加。修法加之前先判空或用 dictConfig 一次性配置。pythonif not logger.handlers: logger.addHandler(h)—## 六、坑 5中文乱码编码问题Windows 高发pythonh logging.FileHandler(app.log) # ❌ Windows 下默认 GBKh logging.FileHandler(app.log, encodingutf-8) # ✅原因FileHandler不指定encoding时用locale.getpreferredencoding()中文 Windows 是GBK/CP936。写 UTF-8 文本进去就乱码或抛UnicodeEncodeError。顺带读取时用open(app.log, encodingutf-8)两边编码一致才不会看花。—## 七、坑 6日志时间不对UTC / 时区pythonimport timeformatter logging.Formatter(%(asctime)s %(message)s)formatter.converter time.localtime # 改成本地时区asctime 默认走 time.localtime其实是本地时间但容器里 TZ 没设置时**容器默认是 UTC**看起来就差 8 小时。 **修法**容器启动时带环境变量 TZAsia/Shanghai或者在代码里指定 converter。---## 八、坑 7多进程写同一个文件丢日志pythonh logging.handlers.RotatingFileHandler(“app.log”, maxBytes10_000_000, backupCount5)**原因**多个进程各自持有文件句柄和偏移量追加写会互相踩。**方案 A推荐**用 concurrent-log-handler 的 ConcurrentRotatingFileHandler文件锁。pythonfrom concurrent_log_handler import ConcurrentRotatingFileHandlerh ConcurrentRotatingFileHandler(“app.log”, maxBytes10_000_000, backupCount5)**方案 B**每个进程写自己的文件app.{pid}.log采集时再合并。---## 九、坑 8f-string 的性能与聚合问题pythonlogger.debug(fuser{user} cost{cost}“) # ❌ 即使不输出也会先格式化logger.debug(“user%s cost%s”, user, cost) # ✅ 惰性格式化**两个理由**① 惰性写法在日志被过滤掉时**不做字符串拼接**省 CPU② 接入 Sentry / ELK 这类系统时模板相同才能**聚合成一条**f-string 会让每个用户都变成独立事件。---## 十、坑 9logger.exception 只能在 except 里用pythontry: risky()except ValueError: logger.exception(“处理失败”) # ✅ 自动带 tracebacklogger.exception()等价于logger.error(…, exc_infoTrue)。在except块外调用Python 3 会提示没有活动异常打印NoneType: None。**另外**捕获后要重新抛出时用logger.error(”…, exc_infoTrue)raise别重复打两遍。---## 十一、可直接抄的配置dictConfig 版 pythonimport logging.configLOGGING { version: 1, disable_existing_loggers: False, # 别让第三方库的 logger 失效 formatters: { std: {format: %(asctime)s %(levelname)s [%(name)s] %(message)s}, }, handlers: { console: {class: logging.StreamHandler, level: INFO, formatter: std}, file: {class: logging.FileHandler, level: DEBUG, formatter: std, filename: app.log, encoding: utf-8}, # ← 编码一定要写 }, loggers: { myapp: {level: DEBUG, handlers: [console, file], propagate: False}, # ← 防止打两遍 },}logging.config.dictConfig(LOGGING)logger logging.getLogger(myapp)**三个必须注意的参数**disable_existing_loggers: False、encoding: “utf-8”、propagate: False。---## 十二、5 分钟排查清单1.logger.level是多少0 表示继承父级2.logger.handlers里有几个**1 就是重复打印的元凶**3.logger.propagate是 True 吗自己配了 handler 就该关掉4. 写文件时传了encoding“utf-8”吗5. 多进程场景用了带锁的 handler 吗---## 小结logging的坑不在 API 难而在于它是**logger handler formatter propagate 四层结构**任何一层配错都会表现成日志不见了或者日志打两遍。 **三条硬记住**① 级别要过两道logger 和 handler②basicConfig只对干净的 root生效③ 自己加了 handler 就把propagate 关掉、且加之前判空。—你最常被哪种症状折磨评论区留个重复打印/不打印/乱码三选一我把出现频率最高的坑再单独展开一篇。