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 模块提供了模块级别的函数,可以快速开始记录日志。

python 复制代码
import logging

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

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

3.2 配置基本日志

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

python 复制代码
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 对象。

python 复制代码
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 日志记录到文件

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

python 复制代码
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):

python 复制代码
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 可以自动捕获并记录异常堆栈。

    python 复制代码
    try:
        1 / 0
    except ZeroDivisionError:
        logger.error("发生了除零错误", exc_info=True) # 等价于 logger.exception(...)
  5. 注意日志级别 :生产环境通常设置为 INFOWARNING,避免 DEBUG 日志的性能开销和信息过载。

  6. 使用 RotatingFileHandler 或 TimedRotatingFileHandler:防止日志文件无限增长,占用磁盘空间。

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

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

python 复制代码
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)和层次结构,遵循最佳实践,你将能够为你的应用程序构建出清晰、有效且可维护的日志系统,从而极大地提升开发、调试和运维效率。

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

相关推荐
quantdash_cc1 小时前
历史数据断层与REST请求慢到崩溃?QuantDash高性能量化数据API终极解决方案
开发语言·python·缓存·php·量化·quantdash
熊野君1 小时前
Prompt / RAG / 规则 / Skills —— 定位、协作与实现路线
开发语言·python
zhangphil2 小时前
Python Web后端框架FastAPI vs Flask
python·ai·llm
sbjdhjd2 小时前
CTF 技术复盘:从参数类型绕过到正则回溯 | Merry Christmas PHP CTF(gift.php & gift_plus.php)
安全·网络安全·云计算·php·ctf·红队·网络攻防
ly76892 小时前
XML 从入门到实践:语法、命名空间、XPath、XSD 与 Java 安全解析
xml·java·python
Sagittarius_A*2 小时前
【好靶场】PHP反序列化入门练习2
开发语言·web安全·信息安全·php·代码审计·反序列化
winfredzhang3 小时前
用 Python + wxPython 造一个照片工具箱:PDF / 加密ZIP / MP4 / 归档,以及我在这过程中踩到的 4 个坑
python·pdf·zip·mp4·移动
卷无止境3 小时前
FastAPI 后台任务的边界,以及 Celery、Redis 与自建调度系统的选择
后端·python