Python logging 模块:从入门到精通

1. 引言

在 Python 开发中,日志记录(logging)是程序调试、监控和问题排查的基石。相比于简单的 print() 语句,Python 标准库中的 logging 模块提供了强大、灵活且可配置的日志系统。无论是小型脚本还是大型分布式应用,合理的日志记录都能显著提升开发效率和运维能力。

本文将带你全面掌握 logging 模块,从基本概念、快速上手,到高级配置、最佳实践,最后通过一个综合示例巩固所学。

2. 核心概念

在深入代码之前,理解 logging 模块的几个核心组件至关重要:

  • Logger(记录器):应用程序直接交互的接口。每个 logger 都有一个名称,通常使用模块名(如 __name__)来创建,形成层次结构。
  • Handler(处理器):决定日志记录的输出目的地,如控制台(StreamHandler)、文件(FileHandler)、网络等。
  • Formatter(格式器):定义日志记录的最终输出格式,包括时间、级别、 logger 名称、消息内容等。
  • Filter(过滤器):提供更细粒度的控制,决定哪些日志记录需要被输出。
  • Log Level(日志级别):定义了日志的严重性。从低到高依次为:
    • DEBUG:详细的调试信息,通常仅在开发时使用。
    • INFO:确认程序按预期运行的一般信息。
    • WARNING:表明发生了一些意外情况,或预示一些问题(如磁盘空间不足),但程序仍在运行。
    • ERROR:由于更严重的问题,程序的某些功能已经无法正常执行。
    • CRITICAL:严重的错误,表明程序本身可能无法继续运行。

3. 快速上手

3.1 基础使用

logging 模块提供了模块级别的函数,可以快速开始记录日志。

import logging

# 记录一条信息级别的日志
logging.info("这是一条 INFO 级别的日志。") # 默认级别是 WARNING,所以这条不会输出
logging.warning("这是一条 WARNING 级别的日志!")

运行上述代码,你只会看到 WARNING 信息。因为 logging 模块的默认级别是 WARNING

3.2 配置基本日志

要输出 INFO 级别的日志,需要先进行基本配置。

import logging

# 配置日志级别和格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

logging.debug("这是一条 DEBUG 日志,你看不到我。")
logging.info("程序启动成功。")
logging.warning("磁盘空间不足 10%。")
logging.error("连接数据库失败。")

basicConfig 是一个便捷的配置方法,但它只在第一次调用时生效。对于更复杂的应用,建议使用更灵活的配置方式。

4. 进阶配置与实践

4.1 使用 Logger 对象

在生产环境中,直接使用模块级函数 (logging.info) 不够灵活。最佳实践是创建和使用命名的 logger 对象。

import logging

# 创建一个 logger,通常以模块名命名
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 设置此 logger 的级别

# 创建一个控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING) # 处理器可以有自己的级别

# 创建一个格式器并添加到处理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)

# 将处理器添加到 logger
logger.addHandler(ch)

# 记录日志
logger.debug('详细的调试信息')
logger.info('一般信息')
logger.warning('警告信息')
logger.error('错误信息')

4.2 日志记录到文件

将日志持久化到文件是常见需求。

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# 文件处理器 - 追加模式
fh = logging.FileHandler('app.log', encoding='utf-8')
fh.setLevel(logging.INFO)
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
fh.setFormatter(file_formatter)

# 控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)
console_formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
ch.setFormatter(console_formatter)

logger.addHandler(fh)
logger.addHandler(ch)

logger.info('这条信息会写入 app.log 文件,但不会在控制台显示(INFO < WARNING)。')
logger.warning('这条警告会同时写入文件和控制台。')

4.3 使用字典或配置文件

对于大型项目,将日志配置与代码分离是更好的选择。logging 模块支持从字典或配置文件加载配置。

字典配置示例 (config.py):

import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout',
        },
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'DEBUG',
            'formatter': 'standard',
            'filename': 'app_debug.log',
            'maxBytes': 10485760, # 10MB
            'backupCount': 5,
            'encoding': 'utf-8'
        },
    },
    'loggers': {
        '': { # root logger
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
        },
        'my_module': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False # 阻止传递给 root logger,避免重复记录
        }
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)

5. 最佳实践与常见陷阱

  1. 使用 __name__ 作为 logger 名称:这能自动反映模块层次,便于过滤和管理。
  2. 避免在模块级别创建 logger:在函数或类内部创建,可以避免在导入时过早初始化。
  3. 谨慎使用 logging.basicConfig:它只在第一次调用时生效,且会添加一个默认的 StreamHandler 到 root logger,可能造成重复日志。
  4. 正确处理异常:使用 logger.exception() 或在记录错误时传入 exc_info=True 可以自动捕获并记录异常堆栈。
    try:
        1 / 0
    except ZeroDivisionError:
        logger.error("发生了除零错误", exc_info=True) # 等价于 logger.exception(...)
    
  5. 注意日志级别:生产环境通常设置为 INFOWARNING,避免 DEBUG 日志的性能开销和信息过载。
  6. 使用 RotatingFileHandler 或 TimedRotatingFileHandler:防止日志文件无限增长,占用磁盘空间。

6. 综合示例:一个简单的 Web 请求日志器

下面是一个模拟 Web 请求处理的综合示例,展示了如何结构化地使用 logging。

import logging
import logging.handlers
import random
import time

# 配置日志
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s [%(levelname)s] %(name)s - %(message)s',
                    handlers=[
                        logging.StreamHandler(),
                        logging.handlers.RotatingFileHandler('web_app.log', maxBytes=1e6, backupCount=3)
                    ])

# 模拟不同模块的 logger
request_logger = logging.getLogger('app.request')
auth_logger = logging.getLogger('app.auth')
db_logger = logging.getLogger('app.db')

def handle_request(user_id, endpoint):
    """模拟处理一个 Web 请求"""
    request_logger.info(f"收到请求: 用户 {user_id}, 端点 {endpoint}")

    # 模拟认证
    if not authenticate(user_id):
        auth_logger.warning(f"用户 {user_id} 认证失败")
        return "Unauthorized", 401

    # 模拟数据库查询
    data = query_database(user_id)
    request_logger.info(f"请求处理完成: {endpoint}")
    return data, 200

def authenticate(user_id):
    time.sleep(0.01)
    # 模拟 90% 的成功率
    success = random.random() > 0.1
    if success:
        auth_logger.debug(f"用户 {user_id} 认证成功")
    return success

def query_database(user_id):
    time.sleep(0.02)
    db_logger.debug(f"查询用户 {user_id} 的数据")
    # 模拟 95% 的成功率
    if random.random() > 0.05:
        return {"user_id": user_id, "data": "some_data"}
    else:
        db_logger.error(f"查询用户 {user_id} 数据时发生数据库错误")
        raise ConnectionError("Database connection lost")

if __name__ == '__main__':
    # 模拟处理 10 个请求
    for i in range(10):
        try:
            result, status = handle_request(user_id=i, endpoint=f"/api/v1/user/{i}")
            print(f"Request {i}: Status {status}")
        except Exception as e:
            request_logger.exception(f"处理请求 {i} 时发生未捕获异常")

7. 总结

Python 的 logging 模块是一个强大而灵活的工具。从简单的 basicConfig 到复杂的多 handler、多 logger 配置,它可以满足各种场景的需求。掌握其核心组件(Logger, Handler, Formatter, Filter)和层次结构,遵循最佳实践,你将能够为你的应用程序构建出清晰、有效且可维护的日志系统,从而极大地提升开发、调试和运维效率。

建议读者根据自己项目的复杂程度,从简单配置开始,逐步过渡到字典或文件配置,并始终将日志视为代码的重要组成部分进行设计和维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值