日志重复打印的根本原因是logger.propagate=true且重复添加handler,导致日志被root和自定义handler各输出一次;应设propagate=false或统一用dictconfig管理。

日志重复打印:为什么 logger.info() 会输出两遍?
根本原因不是代码写错了,而是 logging.getLogger() 返回的 logger 默认自带一个 root handler,而你又手动 addHandler() 了一次。结果同一条日志被 root 和自定义 handler 各刷一遍。
- 每次调用
getLogger("myapp")都返回同一个 logger 实例,但它的传播(propagate)默认是True,会把日志往上传到 root logger - 如果没显式关闭 propagation,又给它加了 handler,就等于“本地打一次 + root 再打一次”
- 常见触发场景:模块里反复调用
getLogger(<strong>name</strong>)并配置 handler,尤其在 import 时执行初始化逻辑
解决方法很简单:
-
logger.propagate = False(推荐,干净利落) - 或者不用
basicConfig(),改用dictConfig()统一管控 root 和子 logger - 不要在一个模块里既调用
basicConfig()又自己addHandler()
日志文件爆炸:RotatingFileHandler 为啥越滚越大?
不是轮转失效,而是默认参数太宽松:maxBytes=0(不触发切割)、backupCount=0(不清理旧文件)。很多项目直接复制示例代码,忘了改这俩值。
-
maxBytes设太小(比如 1KB)会导致频繁切文件,IO 开销大;设太大(比如 100MB)又起不到控量作用 -
backupCount控制保留几个历史文件,设为0就等于只写一个文件,永远不删 - 注意:Windows 下文件重命名可能失败,导致轮转卡住,此时日志会静默丢弃(无报错),看起来像“不写了”
建议配置:
立即学习“Python免费学习笔记(深入)”;
-
maxBytes=10_485_760(10MB)比较平衡 -
backupCount=5足够查最近问题,又不占太多磁盘 - 加上
delay=True,避免启动时就创建空日志文件
异步/多进程下日志错乱:print() 式写法扛不住
用 print() 或直接写文件对象,在并发场景下必然出问题:内容粘连、换行丢失、甚至写入截断。logging 模块本身线程安全,但多进程不默认支持。
-
RotatingFileHandler在多进程下不是原子的,两个进程同时触发轮转,可能删掉对方的备份文件 - 异步框架(如 FastAPI + uvicorn)里,如果在协程里用同步 handler,会阻塞 event loop
- 真正安全的做法只有两种:用
QueueHandler+QueueListener把日志转发到单独线程处理;或者用支持多进程的第三方方案(如concurrent-log-handler)
简单项目可先这样兜底:
- 确保所有日志都走
logging接口,别混用print() - 多进程部署时,让每个 worker 写独立文件(比如加
os.getpid()到文件名) - 不要指望
FileHandler自己搞定跨进程同步
DEBUG 日志泄露:为什么生产环境还在打 SQL 和请求体?
不是没关 DEBUG,而是 logger 级别和 handler 级别是两套开关。比如 logger.setLevel(logging.DEBUG),但 handler 设了 setLevel(logging.WARNING),看起来该过滤,实际仍可能漏——因为某些库(如 sqlalchemy)会用自己的 logger,且默认传播到 root。
- Django、Flask、SQLAlchemy 这些框架内部 logger 名字固定(如
"sqlalchemy.engine"、"urllib3"),必须显式禁用或调级 -
logging.disable(logging.DEBUG)是全局开关,但会关掉所有 DEBUG,包括你真正需要的调试信息 - 更隐蔽的是:某些包在 import 时就注册了 handler,比你的配置还早执行
稳妥做法:
- 在应用启动最开头就调用
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING) - 用
logging.getLogger().handlers = []清掉 root 的默认 handler,再从头配 - 生产环境配置里加一句:
logging.getLogger("urllib3").setLevel(logging.WARNING)
日志量失控从来不是单一配置点的问题,而是 logger 层级、handler 行为、第三方库默认策略、运行时环境四者叠加的结果。最容易被忽略的,是以为“配了 logger 就万事大吉”,却没检查它实际发往哪里、被谁接收、又被谁二次传播。










