文章目录

Python 日志配置:logging 模块的高级设置

发布于 2026-04-10 21:19:57 · 浏览 10 次 · 评论 0 条

Python 日志配置:logging 模块的高级设置

直接使用 print() 函数无法满足生产环境对日志管理的要求,如持久化存储、日志分级和自动轮转。Python 标准库中的 logging 模块提供了强大的日志系统。本指南将手把手教你如何通过代码配置和高级组件,构建一套专业级的日志系统。


1. 理解 logging 的四大核心组件

在编写配置代码前,必须理清四个核心对象的流转关系。日志记录并非简单地“写入文本”,而是一个事件传递的过程。

  1. Logger(记录器):程序代码直接交互的对象。调用 logger.info()logger.error() 发送日志消息。
  2. Handler(处理器):决定日志发往何处。它可以是将日志输出到控制台的 StreamHandler,或者是写入文件的 FileHandler
  3. Formatter(格式化器):决定日志长什么样。定义时间、级别、消息内容的排列顺序。
  4. Filter(过滤器):决定哪些日志被输出。比日志级别更细粒度的控制手段。

这四者的逻辑流转关系如下所示:

graph LR A["Logger: 记录器"] -->|发送日志记录| B["Handler: 处理器"] B -->|应用格式| C["Formatter: 格式化器"] B -->|过滤内容| D["Filter: 过滤器"] C --> E["输出终端: 屏幕/文件/网络"] D --> E

2. 使用字典配置日志系统

相比繁琐的代码式配置(如 logger.addHandler(...)),使用 dictConfig 是更现代、更结构化的方式。它能一次性定义所有组件及其关联。

打开你的 Python 编辑器,新建一个 main.py 文件,输入以下代码来构建一个包含控制台输出和文件输出的基础配置。

导入 logginglogging.config 模块。

import logging
import logging.config

# 1. 定义配置字典
LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False, # 防止覆盖已存在的 logger
    "formatters": {
        "detailed": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S"
        },
        "simple": {
            "format": "%(levelname)s: %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },
        "file": {
            "class": "logging.FileHandler",
            "level": "DEBUG",
            "formatter": "detailed",
            "filename": "app.log",
            "mode": "a",
            "encoding": "utf-8"
        }
    },
    "root": {
        "level": "DEBUG",
        "handlers": ["console", "file"]
    }
}

# 2. 应用配置
logging.config.dictConfig(LOGGING_CONFIG)

# 3. 获取 logger 并测试
logger = logging.getLogger(__name__)

logger.debug("这是调试信息,只会写入文件")
logger.info("这是普通信息,会显示在屏幕并写入文件")
logger.warning("这是警告信息")

运行该脚本。你会在控制台看到简单的输出,同时在同级目录下生成包含详细时间戳的 app.log 文件。


3. 实现日志文件自动轮转

长期运行的程序如果不进行日志切割,单个日志文件可能会撑爆硬盘。使用 RotatingFileHandlerTimedRotatingFileHandler 可以解决此问题。

修改上述 LOGGING_CONFIG 字典中的 handlers 部分,将 file 处理器的类名更改为 logging.handlers.RotatingFileHandler,并添加大小限制参数。

    "handlers": {
        # ... 其他配置保持不变 ...
        "file": {
            "class": "logging.handlers.RotatingFileHandler", # 修改为轮转处理器
            "level": "DEBUG",
            "formatter": "detailed",
            "filename": "app.log",
            "mode": "a",
            "encoding": "utf-8",
            "maxBytes": 10 * 1024 * 1024, # 关键参数:单个文件最大 10MB
            "backupCount": 5              # 关键参数:最多保留 5 个备份文件
        }
    }

保存运行代码。当 app.log 文件大小超过 10MB 时,系统会自动将其重命名为 app.log.1,并创建新的 app.log。备份文件数量达到 5 个后,最旧的文件会被自动删除。总磁盘空间占用计算公式为:

$$ Total Space \approx MaxBytes \times (BackupCount + 1) $$

若需按时间切割(如每天一个文件),使用 logging.handlers.TimedRotatingFileHandler,并将 when 参数设为 midnightD


4. 捕获并记录异常堆栈

仅仅记录“出错了”是不够的,排查问题需要完整的调用栈。logging 模块提供了 exc_info 参数来自动捕获当前异常。

编写一段会产生除零错误的代码来测试此功能。

import logging

# 确保已执行 logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)

def divide_function(x, y):
    try:
        result = x / y
        return result
    except ZeroDivisionError:
        # 关键步骤:设置 exc_info=True
        logger.error("计算过程中发生除零错误", exc_info=True)
        # 也可以使用 logging.exception("..."),它等同于 error(..., exc_info=True)

if __name__ == "__main__":
    divide_function(10, 0)

执行上述代码。查看 app.log 文件,你会发现日志中不仅包含错误信息,还完整打印了 Python 的 Traceback 堆栈信息,精确定位到 divide_function 函数。


5. 实现结构化 JSON 日志

在现代云原生环境中,纯文本日志难以被 ELK(Elasticsearch, Logstash, Kibana)等系统解析。自定义一个 Formatter 来输出 JSON 格式的日志。

定义一个 JsonFormatter 类,继承自 logging.Formatter

import logging
import json
from datetime import datetime

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_record = {
            "timestamp": datetime.utcnow().isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "line": record.lineno
        }

        # 如果有异常信息,添加到 JSON 中
        if record.exc_info:
            log_record["exception"] = self.formatException(record.exc_info)

        return json.dumps(log_record)

# 更新配置字典以使用新的 Formatter
LOGGING_CONFIG = {
    # ... 基础配置 ...
    "formatters": {
        "json": {
            "()": "__main__.JsonFormatter" # 指定自定义类的路径
        }
    },
    "handlers": {
        "json_file": {
            "class": "logging.FileHandler",
            "level": "INFO",
            "formatter": "json",
            "filename": "app.json.log"
        }
    },
    "root": {
        "level": "INFO",
        "handlers": ["json_file"]
    }
}

if __name__ == "__main__":
    logging.config.dictConfig(LOGGING_CONFIG)
    logger = logging.getLogger("Demo")
    logger.info("这是一条 JSON 格式的日志", extra={"custom_field": "额外数据"})

注意:若要在日志中添加额外的动态字段(如用户 ID),使用 extra 参数(如代码最后一行所示),并需在 JsonFormatter 中显式处理 record 里的属性。


6. 配置不同层级 Logger 的隔离

在大型项目中,你可能希望数据库库的日志只记录 WARNING 级别,而业务逻辑记录 DEBUG 级别。配置字典中的 loggers 部分可以实现细粒度控制。

下表展示了如何针对不同模块配置不同的日志级别和处理器。

Logger 名称 级别 处理器 说明
root INFO console, file 默认全局配置,未匹配到的 logger 都走这里
db_connector WARNING file 仅将数据库的警告及以上级别写入文件,不打印到控制台

LOGGING_CONFIG 中添加如下配置:

    "loggers": {
        "db_connector": { # 假设这是你的数据库模块名
            "level": "WARNING",
            "handlers": ["file"], # 只走文件处理器
            "propagate": False   # 关键:禁止传递给 root logger,防止重复记录
        }
    }

设置 propagateFalse 非常重要,否则 db_connector 的日志既会被自己的 handler 处理,又会被上传到 root 再次处理,导致日志重复。

获取 logger 时,使用与配置中相同的名称:

db_logger = logging.getLogger("db_connector")
db_logger.debug("这条日志会被忽略,因为级别不够")
db_logger.warning("数据库连接慢,这条会写入文件")

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文