Python logging模块需显式配置Logger、Handler、Formatter、Filter四层,basicConfig()在root logger初始化后失效;子logger继承父handler但不继承level;多进程须用QueueHandler+QueueListener避免文件冲突;JSON日志应使用python-json-logger库确保ELK兼容。

Python 的 logging 模块不是“配好就能用”的黑盒,它的行为由 Logger、Handler、Formatter、Filter 四层协作决定,任意一层配置错,日志就可能丢失、重复、格式错乱或写到错误位置。
为什么 basicConfig() 有时不生效?
这是最常踩的坑:调用 basicConfig() 前,只要任何代码(包括第三方库)触发过 logging.debug()、logging.info() 等顶层函数,root logger 就已被自动初始化,后续 basicConfig() 直接静默忽略。
- 检查是否在
import logging后立刻调用,且早于所有日志输出 - 更可靠的方式是显式获取并配置 root logger:
root = logging.getLogger() root.setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) root.addHandler(handler) - 使用
logging.getLogger(__name__)获取模块级 logger,避免污染 root
Logger 的层级继承与传播机制
logger 名字用点分隔(如 'a.b.c'),形成树状结构。'a.b.c' 自动继承 'a.b' 和 'a' 的 handler,除非设 propagate=False。
- 默认
propagate=True,导致日志被父 logger 多次处理(比如同时打印到控制台又写入文件) - 子 logger 不会自动继承父 logger 的 level,只继承 handler;自己的 level 决定“是否向上送”,父的 level 决定“是否处理”
- 常见误操作:给
logging.getLogger('myapp.db')设了INFO级别,但 root 是WARNING,结果仍看不到INFO日志——因为 root 拦截了
多进程写同一个日志文件为何出错?
FileHandler 不是进程安全的。多进程直接共用一个 FileHandler 实例,会导致内容错乱、覆盖甚至 OSError: [Errno 9] Bad file descriptor。
立即学习“Python免费学习笔记(深入)”;
- 生产环境必须用
RotatingFileHandler或TimedRotatingFileHandler,并确保每个进程独占 handler 实例 - 更稳妥方案:用
QueueHandler+QueueListener,把日志发到队列,由单个监听进程统一落盘queue = Queue() queue_handler = QueueHandler(queue) logger.addHandler(queue_handler) listener = QueueListener(queue, file_handler) listener.start() # ...程序退出前 listener.stop()
- 避免在 fork 后复用已打开的
FileHandler,子进程应重建 handler
JSON 格式日志怎么写才便于 ELK 采集?
直接 print JSON 字符串不是好办法——时间字段没标准化、异常堆栈被当字符串、level 名称大小写不一致,都会让 Logstash 解析失败。
- 用
logging.Formatter子类重写format(),输出标准 JSON(注意转义和空格) - 关键字段必须存在:
timestamp(ISO8601)、level(大写)、logger(名字)、message、exc_info(结构化) - 推荐轻量方案:
python-json-logger库,一行搞定from pythonjsonlogger import jsonlogger formatter = jsonlogger.JsonFormatter('%(asctime)s %(name)s %(levelname)s %(message)s') handler.setFormatter(formatter)
真正难的不是写日志,而是让日志在分布式、多线程、容器重启、磁盘满等真实场景下依然可查、不丢、不乱——这些细节藏在 handler 生命周期、level 传递逻辑和进程模型里,改一行配置可能就绕过整个链路。










