Python脚本如何区分日志输出级别

wen python案例 28

Python脚本如何区分日志输出级别:从基础到高级的完整指南

目录导读

  1. 为什么需要日志级别控制?
  2. Python内置logging模块的核心概念
  3. 日志级别的定义与含义(DEBUG/INFO/WARNING/ERROR/CRITICAL)
  4. 日志输出级别配置的三种方式
  5. 如何动态调整日志级别
  6. 日志格式化与自定义Handler
  7. 常见陷阱与最佳实践
  8. 问答环节:解决你的实际疑问

为什么需要日志级别控制?

在实际开发中,脚本可能产生大量调试信息、错误报告、状态更新等,如果不加区分地输出所有信息,会导致:

Python脚本如何区分日志输出级别

  • 日志文件迅速膨胀,重要错误被海量调试信息淹没
  • 线上环境不希望看到DEBUG级别日志,但本地调试时需要
  • 无法快速筛选问题(如只关注ERROR级别)

通过日志级别将输出信息分级,是专业Python脚本的必备能力。

Python内置logging模块的核心概念

Python标准库logging提供了完善的日志系统,主要组件包括:

  • Logger:日志记录器,应用程序直接调用它的info()debug()等方法
  • Handler:日志处理器,决定日志输出到控制台、文件或网络
  • Formatter:格式化器,定义日志内容的格式(时间、级别、消息等)
  • Filter:过滤器,实现更精细的级别控制

层级关系:Logger通过setLevel()设置级别,Handler也独立拥有级别,最终输出取两者的“较高”级别(即数值较大、更严重的级别)。

日志级别的定义与含义

Python定义了五个标准级别(数值从小到大,严重程度递增):

级别 数值 使用场景
DEBUG 10 调试信息,如变量值、函数调用
INFO 20 普通信息,如程序启动、完成
WARNING 30 警告信息,如配置文件缺失
ERROR 40 错误信息,如数据库连接失败
CRITICAL 50 严重错误,如内存泄漏

注意:设置级别为INFO时,DEBUG级别的信息不会输出,而WARNING及更高级别的会输出。

日志输出级别配置的三种方式

basicConfig() 快速配置

import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.info("程序启动")
logging.debug("调试信息")  # 不会输出,因为级别为INFO

Logger对象手动配置

logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)
# 控制台Handler
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)  # 只输出WARNING及以上
logger.addHandler(ch)
# 文件Handler
fh = logging.FileHandler('app.log')
fh.setLevel(logging.ERROR)   # 只记录错误
logger.addHandler(fh)

配置文件(适合大型项目)

# logging.conf
[loggers]
keys=root
[logger_root]
level=INFO
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=WARNING
formatter=basicFormatter
[formatters]
keys=basicFormatter
[formatter_basicFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

然后通过logging.config.fileConfig('logging.conf')加载。

如何动态调整日志级别

生产环境中,经常需要在不重启进程的情况下改变日志级别。

import logging
def set_logger_level(logger_name: str, level: int):
    logger = logging.getLogger(logger_name)
    logger.setLevel(level)
    for handler in logger.handlers:
        handler.setLevel(level)

更高级的做法是结合信号处理或配置中心,比如监听SIGUSR1信号:

import signal
def handle_signal(signum, frame):
    logging.getLogger().setLevel(logging.DEBUG)
signal.signal(signal.SIGUSR1, handle_signal)

日志格式化与自定义Handler

自定义格式化示例

formatter = logging.Formatter(
    '%(asctime)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

自定义Handler(如输出到Redis)

class RedisHandler(logging.Handler):
    def __init__(self, redis_client, key='logs'):
        super().__init__()
        self.redis = redis_client
        self.key = key
    def emit(self, record):
        msg = self.format(record)
        self.redis.lpush(self.key, msg)

常见陷阱与最佳实践

陷阱一:忘记设置root logger的级别

# 错误示例
logging.basicConfig()  # 默认级别为WARNING
logging.info("不会输出")  # 结果:没有输出

解决:明确设置level=logging.DEBUG或更低级别。

陷阱二:多次调用basicConfig()

logging.basicConfig(level=logging.INFO)  # 第一次
logging.basicConfig(level=logging.DEBUG)  # 第二次无效

解决:使用logger = logging.getLogger(__name__)并在应用入口一次性配置。

最佳实践

  • 每个模块使用logger = logging.getLogger(__name__)获取独立logger
  • 生产环境日志保留INFO及以上级别,开发环境可开启DEBUG
  • 日志文件使用滚动机制(RotatingFileHandler)防止无限增长
  • 敏感信息(密码、密钥)绝对不要记录在日志中

问答环节:解决你的实际疑问

问:为什么我设置了logger.setLevel(logging.DEBUG),但控制台还是看不到DEBUG信息? 答:检查是否添加了Handler,以及Handler的级别是否高于DEBUG,如果只添加了StreamHandler而没设置其级别,默认是WARNING,需要在Handler上显式调用setLevel(logging.DEBUG)

问:如何只在特定函数内部临时提升日志级别? 答:可以使用上下文管理器:

import contextlib
@contextlib.contextmanager
def logging_level(level):
    old_level = logging.getLogger().level
    logging.getLogger().setLevel(level)
    try:
        yield
    finally:
        logging.getLogger().setLevel(old_level)
with logging_level(logging.ERROR):
    # 这段代码内只输出ERROR及以上
    ...

问:多线程脚本中日志级别如何处理? 答:logging是线程安全的,可以直接在多线程中使用,但注意日志输出顺序可能交错,建议使用QueueHandler+QueueListener组合实现线程安全的异步日志。

问:如何根据环境(开发/测试/生产)自动设置级别? 答:通过环境变量:

import os
log_level = os.getenv('LOG_LEVEL', 'WARNING').upper()
logging.basicConfig(level=getattr(logging, log_level, logging.WARNING))

通过合理运用日志级别,你的Python脚本将获得专业级的可观测性,好的日志系统能让故障定位时间缩短80%以上,从今天开始,在每一个print()出现的地方,思考它应该属于哪个级别吧!

抱歉,评论功能暂时关闭!