logger.level必须设为最低需求级别(如DEBUG),否则低级别日志在到达handler前就被过滤;各handler再通过setLevel或addFilter独立控制输出。

为什么直接设置 logger.level 不起作用
很多人以为给 logger 设置一个级别(比如 logger.setLevel(logging.DEBUG)),再分别给 handler 设不同级别,就能让控制台只输出 WARNING 以上、文件输出 DEBUG 以上——结果发现控制台还是刷了一堆 DEBUG 日志。这是因为日志事件在到达 handler 前,先被 logger 的 level 过滤了一次:如果 logger.level 是 WARNING,那 DEBUG 和 INFO 根本不会往下传,handler 再怎么设也收不到。
正确做法是:把 logger.level 设为最低需要的级别(通常是 DEBUG),把真正的过滤逻辑交给各个 Handler 自己控制。
用 addFilter 给 Handler 单独设级别
Python 的 Handler 支持添加过滤器,最简单的方式就是用内置的 logging.Filter 子类或匿名函数。关键点在于:每个 handler 只处理自己关心的级别,互不干扰。
示例配置:
立即学习“Python免费学习笔记(深入)”;
import logging
<p>logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG) # ← 必须设为最低需求级别</p><h1>控制台只输出 WARNING 及以上</h1><p>console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING) # ← handler 自己的 level 生效
console_handler.setFormatter(logging.Formatter("%(levelname)s - %(message)s"))</p><h1>文件输出 DEBUG 及以上</h1><p>file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG) # ← 这里设低没问题
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))</p><p>logger.addHandler(console_handler)
logger.addHandler(file_handler)
注意:console_handler.setLevel(...) 在这里完全有效——因为 StreamHandler 继承自 Handler,它自己的 level 就是针对“该 handler 是否处理当前日志”的开关。
常见错误:误用 basicConfig 覆盖已有配置
如果项目中某处调用了 logging.basicConfig()(哪怕只是 import 第三方库时悄悄触发的),它会自动给 root logger 添加一个 handler,并可能把 level 设成 WARNING 或更高。这时你后续手动加的 handler 可能被“静默压制”——尤其当你没显式指定 logger 名称、又用的是 logging.getLogger()(即 root logger)时。
规避方法:
- 始终用带名字的 logger:
logging.getLogger("myapp"),避免和 root logger 混淆 - 在配置前检查是否已有 handlers:
if not logger.handlers: - 绝对不要在配置完成后还调用
basicConfig,它只生效一次,但会破坏你精心设计的多 handler 结构
进阶:用 filter 实现更灵活的路由逻辑
如果不止是按级别分流,还想按模块名、消息内容、甚至自定义字段(比如是否含敏感词)来决定是否写入某 handler,就得写自定义 filter。
例如,只让包含 "auth" 的日志进文件,其余进控制台:
class AuthFilter(logging.Filter):
def filter(self, record):
return "auth" in record.getMessage().lower()
<p>file_handler.addFilter(AuthFilter())
这种 filter 返回 True 表示“允许该 record 进入此 handler”,返回 False 则跳过。多个 filter 会按添加顺序依次执行,全为 True 才放行。
真正容易被忽略的是:filter 是绑定在 handler 上的,不是 logger;而且它运行在 handler 的 emit 阶段,此时 record 已经格式化完毕,record.msg 还是原始参数,record.message 才是最终字符串——别在 filter 里错用字段名。


















