Python 日志配置:logging 模块的高级设置
直接使用 print() 函数无法满足生产环境对日志管理的要求,如持久化存储、日志分级和自动轮转。Python 标准库中的 logging 模块提供了强大的日志系统。本指南将手把手教你如何通过代码配置和高级组件,构建一套专业级的日志系统。
1. 理解 logging 的四大核心组件
在编写配置代码前,必须理清四个核心对象的流转关系。日志记录并非简单地“写入文本”,而是一个事件传递的过程。
- Logger(记录器):程序代码直接交互的对象。调用
logger.info()或logger.error()发送日志消息。 - Handler(处理器):决定日志发往何处。它可以是将日志输出到控制台的
StreamHandler,或者是写入文件的FileHandler。 - Formatter(格式化器):决定日志长什么样。定义时间、级别、消息内容的排列顺序。
- Filter(过滤器):决定哪些日志被输出。比日志级别更细粒度的控制手段。
这四者的逻辑流转关系如下所示:
2. 使用字典配置日志系统
相比繁琐的代码式配置(如 logger.addHandler(...)),使用 dictConfig 是更现代、更结构化的方式。它能一次性定义所有组件及其关联。
打开你的 Python 编辑器,新建一个 main.py 文件,输入以下代码来构建一个包含控制台输出和文件输出的基础配置。
导入 logging 和 logging.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. 实现日志文件自动轮转
长期运行的程序如果不进行日志切割,单个日志文件可能会撑爆硬盘。使用 RotatingFileHandler 或 TimedRotatingFileHandler 可以解决此问题。
修改上述 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 参数设为 midnight 或 D。
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,防止重复记录
}
}
设置 propagate 为 False 非常重要,否则 db_connector 的日志既会被自己的 handler 处理,又会被上传到 root 再次处理,导致日志重复。
获取 logger 时,使用与配置中相同的名称:
db_logger = logging.getLogger("db_connector")
db_logger.debug("这条日志会被忽略,因为级别不够")
db_logger.warning("数据库连接慢,这条会写入文件")
暂无评论,快来抢沙发吧!