Python3 logging 模块

在 Python 开发中,logging 模块是官方标准库提供的日志记录工具,相比 print 语句,它支持日志分级、多目的地输出(控制台 / 文件 / 网络)、格式化配置、日志轮转等高级功能,是开发、调试、运维阶段不可或缺的核心工具。本文将从基础概念到实战场景,全面解析 logging 模块的使用方法。

一、核心概念:日志的核心组件与级别

1. 四大核心组件(工作流程)

logging 模块的日志处理流程依赖四大组件,各司其职且层层关联:
 
  • Logger(日志器):日志的入口,供开发者调用(如 logger.info()),负责收集日志请求并传递给 Handler。
  • Handler(处理器):决定日志的输出目的地(控制台、文件、邮件等),一个 Logger 可绑定多个 Handler(如同时输出到控制台和文件)。
  • Formatter(格式化器):定义日志的输出格式(如包含时间、模块、级别、消息内容)。
  • Filter(过滤器):对日志进行二次筛选(如仅记录特定模块、特定关键字的日志),可选组件。
 
工作流程:开发者调用 logger 方法 → Logger 接收请求并判断级别 → 符合级别要求的日志通过 Filter → 传递给所有绑定的 Handler → Handler 按 Formatter 格式输出到目标位置。

2. 日志级别(优先级从低到高)

logging 定义了 5 个标准级别,默认仅输出 WARNING 及以上级别的日志(可自定义阈值):
 
级别名称数值用途说明
DEBUG 10 调试信息(如变量值、函数执行流程),仅开发阶段使用。
INFO 20 正常运行信息(如程序启动、服务连接成功),记录关键流程节点。
WARNING 30 警告信息(如参数不匹配、资源不足),程序仍可运行但需关注。
ERROR 40 错误信息(如函数调用失败、文件读取异常),部分功能无法正常执行。
CRITICAL 50 严重错误(如数据库连接失败、内存耗尽),程序即将崩溃。
 
扩展说明:支持自定义日志级别,但不推荐(破坏通用性);日志级别可通过数值比较(如 level >= WARNING 即输出)。

二、基础使用:快速上手

1. 最简单的日志输出(直接使用模块级函数)

logging 模块提供了与日志级别对应的模块级函数(debug()info()warning() 等),无需复杂配置即可快速输出日志:
  
import logging

# 直接调用模块级函数(默认输出到控制台,格式为:WARNING:root:警告信息)
logging.debug("这是 DEBUG 级日志(默认不输出)")
logging.info("这是 INFO 级日志(默认不输出)")
logging.warning("这是 WARNING 级日志(默认输出)")
logging.error("这是 ERROR 级日志")
logging.critical("这是 CRITICAL 级日志")
 
 
输出结果
 
WARNING:root:这是 WARNING 级日志(默认输出)
ERROR:root:这是 ERROR 级日志
CRITICAL:root:这是 CRITICAL 级日志
 

2. 基础配置(basicConfig()

通过 logging.basicConfig() 可快速配置日志的输出格式、级别、目的地等,仅需调用一次(通常在程序入口):
  
import logging

# 基础配置(必须在首次调用日志函数前执行)
logging.basicConfig(
    level=logging.DEBUG,  # 输出所有级别 >= DEBUG 的日志
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",  # 日志格式
    datefmt="%Y-%m-%d %H:%M:%S",  # 时间格式
    handlers=[
        logging.StreamHandler(),  # 输出到控制台(默认)
        logging.FileHandler("app.log", encoding="utf-8")  # 输出到文件(UTF-8 编码避免中文乱码)
    ]
)

# 测试日志输出
logging.debug("调试:用户请求参数为 {'id': 1}")
logging.info("信息:服务启动成功,监听端口 8080")
logging.warning("警告:磁盘空间剩余 10%,请及时清理")
logging.error("错误:数据库查询失败,SQL: SELECT * FROM user")
logging.critical("严重:数据库连接超时,程序即将退出")
 
 
关键参数说明
 
  • level:设置日志阈值,低于该级别的日志不输出。
  • format:日志格式占位符(常用):
    • %(asctime)s:日志产生时间。
    • %(name)s:日志器名称(默认 root)。
    • %(levelname)s:日志级别名称(大写)。
    • %(message)s:日志内容。
    • %(filename)s:产生日志的文件名。
    • %(lineno)d:产生日志的代码行号。
  • handlers:指定日志处理器(可同时输出到控制台和文件)。
  • encoding:文件输出时指定编码(避免中文乱码)。

三、进阶配置:自定义 Logger 与 Handler

模块级函数本质是使用默认的 root Logger,实际开发中推荐自定义 Logger(避免模块间日志冲突),灵活组合 Handler、Formatter 和 Filter。

1. 自定义 Logger 示例(控制台 + 文件双输出)

import logging

def setup_custom_logger():
    # 1. 创建 Logger(名称建议用模块名 __name__,避免冲突)
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)  # Logger 级别(需低于 Handler 级别才会生效)
    logger.propagate = False  # 禁止传递给父 Logger(避免重复输出)

    # 2. 创建控制台 Handler(输出到终端)
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)  # 控制台仅输出 >= INFO 的日志

    # 3. 创建文件 Handler(输出到日志文件)
    file_handler = logging.FileHandler("custom.log", encoding="utf-8")
    file_handler.setLevel(logging.DEBUG)  # 文件输出 >= DEBUG 的日志(包含调试信息)

    # 4. 创建 Formatter(定义日志格式)
    console_formatter = logging.Formatter(
        "%(asctime)s - %(levelname)s - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )
    file_formatter = logging.Formatter(
        "%(asctime)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s"
    )

    # 5. 给 Handler 绑定 Formatter
    console_handler.setFormatter(console_formatter)
    file_handler.setFormatter(file_formatter)

    # 6. 给 Logger 绑定 Handler
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)

    return logger

# 使用自定义 Logger
logger = setup_custom_logger()
logger.debug("调试:进入用户登录函数")
logger.info("信息:用户 'admin' 登录成功")
logger.warning("警告:用户密码复杂度不足")
logger.error("错误:用户登录失败,密码错误")
logger.critical("严重:连续 5 次登录失败,账户锁定")
 
 
核心逻辑
 
  • Logger 级别是 “入口阈值”,Handler 级别是 “出口阈值”,需同时满足 Logger 级别 <= 日志级别 <= Handler 级别 才会输出。
  • logger.propagate = False 避免日志被传递到 root Logger,导致重复输出。

2. 日志轮转(避免日志文件过大)

当程序长期运行时,单一日志文件会越来越大,logging.handlers 提供了两种轮转机制:

(1)按文件大小轮转(RotatingFileHandler) 

import logging
from logging.handlers import RotatingFileHandler

# 创建按大小轮转的 Handler
# maxBytes=1024*1024:单个文件最大 1MB;backupCount=5:保留最近 5 个日志文件
rotating_handler = RotatingFileHandler(
    "size_rotated.log",
    maxBytes=1024 * 1024,  # 1MB
    backupCount=5,
    encoding="utf-8"
)

# 绑定到 Logger
logger = logging.getLogger("size_logger")
logger.setLevel(logging.INFO)
logger.addHandler(rotating_handler)

# 测试:循环输出日志,触发轮转
for i in range(10000):
    logger.info(f"按大小轮转测试 - 第 {i} 条日志")
 

(2)按时间轮转(TimedRotatingFileHandler) 

import logging
from logging.handlers import TimedRotatingFileHandler

# 创建按时间轮转的 Handler
# when="D":按天轮转;interval=1:每天 1 次;backupCount=7:保留最近 7 天日志
timed_handler = TimedRotatingFileHandler(
    "time_rotated.log",
    when="D",  # 轮转单位:S(秒)、M(分)、H(时)、D(天)、W0-W6(星期)、midnight(午夜)
    interval=1,  # 间隔时间(结合 when)
    backupCount=7,  # 保留日志文件数
    encoding="utf-8",
    utc=True  # 是否使用 UTC 时间(默认本地时间)
)

# 绑定到 Logger
logger = logging.getLogger("time_logger")
logger.setLevel(logging.INFO)
logger.addHandler(timed_handler)

# 测试
logger.info("按时间轮转测试 - 每日生成新日志文件")
 

3. 异常日志记录(关键!)

logging 支持自动记录异常堆栈信息,只需在 except 块中调用 logger.exception() 或传入 exc_info=True
  
import logging

logging.basicConfig(
    level=logging.ERROR,
    filename="error.log",
    format="%(asctime)s - %(levelname)s - %(message)s",
    encoding="utf-8"
)

try:
    # 模拟异常
    result = 10 / 0
except Exception as e:
    # 方式 1:logger.exception() 自动记录堆栈信息
    logging.exception("除法运算异常:")
    # 方式 2:传入 exc_info=True
    # logging.error(f"除法运算异常:{e}", exc_info=True)
 
 
日志文件输出(包含堆栈)
 
2024-05-20 15:30:00,123 - ERROR - 除法运算异常:
Traceback (most recent call last):
  File "test.py", line 10, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero
 

四、实战场景:多模块日志配置

大型项目通常包含多个模块,推荐使用 “配置文件” 或 “字典配置” 统一管理日志,避免重复代码。

1. 字典配置(推荐,无需配置文件) 

import logging
from logging.config import dictConfig

# 日志配置字典
LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,  # 不禁用已存在的 Logger
    "formatters": {
        "simple": {
            "format": "%(asctime)s - %(levelname)s - %(message)s"
        },
        "detailed": {
            "format": "%(asctime)s - %(name)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "simple"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "DEBUG",
            "formatter": "detailed",
            "filename": "project.log",
            "maxBytes": 1024 * 1024 * 5,  # 5MB
            "backupCount": 10,
            "encoding": "utf-8"
        }
    },
    "loggers": {
        # 自定义 Logger:匹配模块名 "my_project" 及其子模块
        "my_project": {
            "level": "DEBUG",
            "handlers": ["console", "file"],
            "propagate": False
        },
        # 默认 root Logger
        "root": {
            "level": "WARNING",
            "handlers": ["console"]
        }
    }
}

# 加载配置
dictConfig(LOGGING_CONFIG)

# 模块 A 中使用
logger_a = logging.getLogger("my_project.module_a")
logger_a.debug("模块 A 调试信息")
logger_a.info("模块 A 启动成功")

# 模块 B 中使用
logger_b = logging.getLogger("my_project.module_b")
logger_b.error("模块 B 执行失败")
 

2. 配置文件(.conf 格式)

创建 logging.conf 文件:
 
[loggers]
keys=root,my_project

[logger_root]
level=WARNING
handlers=console

[logger_my_project]
level=DEBUG
handlers=console,file
propagate=0
qualname=my_project

[handlers]
keys=console,file

[handler_console]
class=StreamHandler
level=INFO
formatter=simple
args=(sys.stdout,)

[handler_file]
class=logging.handlers.RotatingFileHandler
level=DEBUG
formatter=detailed
args=("project.log", "a", 5*1024*1024, 10, "utf-8")

[formatters]
keys=simple,detailed

[formatter_simple]
format=%(asctime)s - %(levelname)s - %(message)s

[formatter_detailed]
format=%(asctime)s - %(name)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s
 
 
加载配置文件: 
import logging
from logging.config import fileConfig

# 加载配置文件
fileConfig("logging.conf")

# 使用 Logger
logger = logging.getLogger("my_project")
logger.info("从配置文件加载日志配置成功")
 

五、注意事项与最佳实践

1. 常见误区

  • 重复输出日志:未设置 logger.propagate = False,导致日志同时被自定义 Logger 和 root Logger 输出,需禁用传播或调整父 Logger 配置。
  • 中文乱码:文件 Handler 未指定 encoding="utf-8",需在创建 FileHandler 时显式设置编码。
  • 日志级别失效:Logger 级别高于 Handler 级别(如 Logger 设为 INFO,Handler 设为 DEBUG),此时 Handler 无法接收 DEBUG 级日志,需确保 Logger 级别 <= Handler 级别。
  • 模块级 Logger 冲突:多个模块使用相同名称的 Logger,导致配置相互影响,推荐使用 __name__ 作为 Logger 名称(如 logging.getLogger(__name__)),自动区分模块。

2. 最佳实践

  • 统一配置:大型项目使用字典配置或配置文件,集中管理日志格式、级别、输出目的地。
  • 合理分级:开发环境输出 DEBUG 级日志,生产环境仅输出 WARNING 及以上级别(减少日志量)。
  • 日志轮转:生产环境必须配置日志轮转(按大小 / 时间),避免日志文件占用过多磁盘空间。
  • 包含关键信息:日志格式应包含时间、模块、级别、行号(便于问题定位)。
  • 异常必记录堆栈:捕获异常时使用 logger.exception() 或 exc_info=True,保留堆栈信息便于调试。
  • 避免日志泄露敏感信息:不记录密码、Token 等敏感数据,必要时脱敏(如 password: ****)。

六、总结

logging 模块是 Python 日志管理的标准解决方案,核心优势在于灵活的配置和丰富的功能。从基础的模块级函数到自定义 Logger、日志轮转、多模块配置,可满足从小型脚本到大型项目的所有日志需求。
 
使用时需牢记:
 
  1. 日志级别决定输出范围,合理设置避免日志冗余或缺失。
  2. 四大组件(Logger/Handler/Formatter/Filter)的协作关系是配置核心。
  3. 生产环境务必配置日志轮转和编码,避免中文乱码和磁盘占满。

posted on 2025-12-04 09:05  小陶coding  阅读(0)  评论(0)    收藏  举报