日志是排查问题的重要手段,但当日志量失控时,它反而会成为负担。Python的logging模块采用层级结构和多Handler分发的设计,屏蔽某一级别的输出并不是简单调一个参数就完事,理解Logger、Handler、Filter三者的关系,才能做到精准管控。本文从原理到代码,详细讲解几种常用的屏蔽手段。

一、先弄清楚日志级别和层级结构的生效规则
logging模块内置了六个级别,从低到高分别是DEBUG、INFO、WARNING、ERROR、CRITICAL,对应的数值是10、30、40、50。一条日志能否被输出,取决于两个门槛:第一是Logger自身的级别,日志事件的级别必须大于等于Logger级别才会被处理;第二是Handler的级别,即使Logger放行了,Handler级别不够也会拦截。这两个条件缺一不可。
另外一个关键机制是层级继承。logging模块中的Logger是以点号分隔的命名树,比如myapp.db这个Logger会自动成为myapp的子Logger。子Logger的日志事件处理完之后,会沿着层级向上传播,交给所有祖先Logger的Handler处理,这就是为什么很多情况下你明明没有给某个Logger添加Handler,日志还是被打出来了,实际是root logger的Handler在干活。
理解了这一点就明白:屏蔽输出时只改Logger级别可能无效,因为传播到父Logger后父Logger的Handler还会再次输出。这也是很多人配置了半天下面的代码没生效的根本原因。
二、用setLevel和Handler级别实现基础屏蔽
最直接的方式是调用setLevel方法。假设你只想屏蔽DEBUG信息,保留WARNING及以上级别,可以这样写:
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('myapp')
# 只想临时屏蔽DEBUG级别,把级别提高到INFO即可
logger.setLevel(logging.INFO)
logger.debug('这条DEBUG日志会被屏蔽')
logger.warning('这条WARNING日志正常输出')如果要屏蔽的是某个输出目标而非全局,比如文件里记录所有级别但控制台只显示ERROR,那么应该给不同的Handler设置不同级别。Logger自身的级别保持DEBUG不动,由Handler各自决定放行范围:
import logging
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# 控制台Handler只输出ERROR及以上
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.ERROR)
# 文件Handler记录所有级别
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.debug('只进文件,不上控制台')
logger.error('两个目标都会输出')这种按Handler独立配置的方式在实际项目中非常常用,本质上实现了日志分流:重要的内容给人看,完整的记录给文件留存。需要注意的是,Handler级别只能做大于等于的放行判断,无法屏蔽中间某一级别,比如想要输出DEBUG但屏蔽INFO,这时候就需要Filter出场了。
三、用Filter精准屏蔽特定级别和指定来源
Filter比级别控制灵活得多,它可以基于任意条件决定日志的去留。自定义一个Filter类,实现filter方法返回True或False即可:
import logging
class BlockInfoFilter(logging.Filter):
def filter(self, record):
# record.levelno是日志的数值级别,屏蔽INFO(20),其余放行
return record.levelno != logging.INFO
logger = logging.getLogger('myapp')
handler = logging.StreamHandler()
handler.addFilter(BlockInfoFilter())
logger.addHandler(handler)
logger.debug('DEBUG正常输出')
logger.info('INFO被屏蔽')
logger.warning('WARNING正常输出')Filter还可以挂在Logger上,并且能通过record.name判断日志来源,实现屏蔽某个第三方库日志的效果。比如requests库底层的urllib3总爱输出无关紧要的WARNING,可以这样处理:
import logging
logging.basicConfig(level=logging.DEBUG)
# 直接把urllib3的日志级别抬高,屏蔽其DEBUG和INFO输出
logging.getLogger('urllib3').setLevel(logging.WARNING)
# 或者用propagate关闭向上传播,彻底屏蔽该库的所有输出
logging.getLogger('urllib3').propagate = False把propagate设置为False是一个粗暴但有效的手段,它会切断日志向父Logger传播的路径,常用于彻底静音某个噪音模块。区别在于setLevel是按级别过滤,propagate是连根拔起,按需选择即可。
四、全局开关和容易踩的坑
有时候需要临时关闭全部日志输出,比如在生产环境的某个入口处。logging模块提供了logging.disable方法:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# 屏蔽INFO及以下的所有输出,只留WARNING以上
logging.disable(logging.INFO)
logger.debug('被屏蔽')
logger.info('被屏蔽')
logger.error('正常输出')
# 恢复时传入NOTSET
logging.disable(logging.NOTSET)这个方法优先级高于一切Logger和Handler的级别设置,适合做全局兜底,但不适合精细控制。真正容易出问题的是以下几个坑:第一,忘记配置root logger时,Python 3.2以后默认会输出WARNING及以上到控制台,你会发现想屏蔽的INFO打不出来但WARNING拦不住,解决办法是显式调用logging.basicConfig(level=logging.DEBUG)先接管root的配置;第二,重复调用addHandler导致同一条日志输出多次,初始化日志前先判断logger.handlers是否为空;第三,在多进程中直接用FileHandler写同一个文件会导致日志交错,应该使用QueueHandler加单独的写进程。
最后推荐一个规范做法:在项目入口统一初始化日志,通过环境变量控制级别,开发环境用DEBUG,线上环境屏蔽到WARNING,这样一套代码就能适配不同场景的输出需求,日志系统才算真正做到可控可用。
Python日志屏蔽logging模块日志级别修改时间:2026-09-14 23:26:39