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 = 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,避免污染 rootLogger 的层级继承与传播机制logger 名字用点分隔(如 'a.b.c'),形成树状结构。'a.b.c' 自动继承 'a.b' 和 'a' 的 handler,除非设 propagate=False。
propagate=True,导致日志被父 logger 多次处理(比如同时打印到控制台又写入文件)logging.getLogger('myapp.db') 设了 INFO 级别,但 root 是 WARNING,结果仍看不到 INFO 日志——因为 root 拦截了FileHandler 不是进程安全的。多进程直接共用一个 FileHandler 实例,会导致内容错乱、覆盖甚至 OSError: [Errno 9] Bad file descriptor。
RotatingFileHandler 或 TimedRotatingFileHandler,并确保每个进程独占 handler 实例QueueHandler + QueueListener,把日志发到队列,由单个监听进程统一落盘queue = Queue() queue_handler = QueueHandler(queue) logger.addHandler(queue_handler) listener = QueueListener(queue, file_handler) listener.start() # ...程序退出前 listener.stop()
FileHandler,子进程应重建 handler直接 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 传递逻辑和进程模型里,改一行配置可能就绕过整个链路。