查看: 144|回复: 3

Python logging搭建工程级日志系统实战指南

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
很多Python开发者习惯用print调试,但到了生产环境,print无法控制日志级别、持久化和格式化,排查问题非常困难。logging模块是Python标准库提供的日志系统,支持分级输出、文件记录和格式自定义,是替代print的正确选择。

## 快速入门:五大组件

logging模块由五个核心组件构成:Logger(日志入口,负责分级记录)、Handler(输出目的地,如文件或终端)、Formatter(定义日志格式)、Filter(按条件过滤日志)、Level(日志级别,从低到高为DEBUG、INFO、WARNING、ERROR、CRITICAL)。理解这些组件有助于灵活配置日志系统。

## 一行配置即插即用

对于小型项目,使用basicConfig快速完成配置,同时输出到终端和文件:
  1. import logging
  2. logging.basicConfig(
  3.     level=logging.INFO,
  4.     format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
  5.     datefmt="%Y-%m-%d %H:%M:%S",
  6.     handlers=[
  7.         logging.StreamHandler(),
  8.         logging.FileHandler("app.log", encoding="utf-8"),
  9.     ],
  10. )
  11. log = logging.getLogger(__name__)
  12. log.info("服务启动成功")
  13. log.warning("磁盘使用率超过 80%%")
  14. log.error("数据库连接超时", exc_info=True)  # 自动打印堆栈
复制代码
注意:exc_info=True可以在ERROR级别自动输出异常堆栈,便于定位问题。

## 日志轮转防止磁盘撑爆

文件日志无限增长会耗尽磁盘空间。使用RotatingFileHandler按大小自动切割:
  1. from logging.handlers import RotatingFileHandler
  2. handler = RotatingFileHandler(
  3.     "app.log",
  4.     maxBytes=10 * 1024 * 1024,  # 10MB
  5.     backupCount=5,  # 保留5个备份
  6.     encoding="utf-8",
  7. )
  8. handler.setFormatter(logging.Formatter(
  9.     "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
  10. ))
复制代码
当文件达到10MB时,handler自动重命名并创建新文件,保留最近5个历史备份。

## 按模块分层管理

大型项目不应使用单一全局Logger,应为每个模块创建独立Logger,方便定位日志来源:
  1. # utils/db.py
  2. logger = logging.getLogger("app.db")
  3. # utils/http.py
  4. logger = logging.getLogger("app.http")
复制代码
根Logger(如"app")统一配置后,子Logger自动继承,无需重复设置Handler和格式。

## 高级:输出JSON格式日志

对接ELK等日志平台时,JSON格式更易解析。自定义Formatter将日志转换为JSON字符串:
  1. import json
  2. import logging
  3. class JsonFormatter(logging.Formatter):
  4.     def format(self, record: logging.LogRecord) -> str:
  5.         log_obj = {
  6.             "time": self.formatTime(record, self.datefmt),
  7.             "level": record.levelname,
  8.             "logger": record.name,
  9.             "message": record.getMessage(),
  10.         }
  11.         if record.exc_info and record.exc_info[0]:
  12.             log_obj["exception"] = self.formatException(record.exc_info)
  13.         return json.dumps(log_obj, ensure_ascii=False)
复制代码
将JsonFormatter设置给Handler后,每行日志输出一个合规的JSON对象。

## 避坑指南

- 不要使用空参getLogger(),应传入__name__以明确模块来源。
- 消息格式优先使用"%s" % user的惰性求值,避免不必要的格式化开销。
- 重复添加Handler会导致日志重复输出,可在添加前检查logger.handlers是否为空。
- 文件路径包含中文时,务必指定FileHandler的encoding="utf-8"防止乱码。
- 生产环境日志级别应通过环境变量控制:getattr(logging, os.getenv("LOG_LEVEL", "INFO"))。

## 开箱即用模板

以下函数封装了终端(仅WARNING及以上)和文件(含轮转)的完整配置,适合直接集成到项目中:
  1. import logging
  2. import logging.handlers
  3. import sys
  4. from pathlib import Path
  5. def setup_logger(
  6.     name: str = __name__,
  7.     level: str = "INFO",
  8.     log_file: str | None = None,
  9. ) -> logging.Logger:
  10.     logger = logging.getLogger(name)
  11.     if logger.handlers:
  12.         return logger
  13.     logger.setLevel(getattr(logging, level.upper(), logging.INFO))
  14.     fmt = logging.Formatter(
  15.         "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d: %(message)s"
  16.     )
  17.     console = logging.StreamHandler(sys.stdout)
  18.     console.setLevel(logging.WARNING)
  19.     console.setFormatter(fmt)
  20.     logger.addHandler(console)
  21.     if log_file:
  22.         Path(log_file).parent.mkdir(parents=True, exist_ok=True)
  23.         handler = logging.handlers.RotatingFileHandler(
  24.             log_file, maxBytes=10 << 20, backupCount=5, encoding="utf-8"
  25.         )
  26.         handler.setLevel(logging.DEBUG)
  27.         handler.setFormatter(fmt)
  28.         logger.addHandler(handler)
  29.     return logger
  30. log = setup_logger("my_app", level="DEBUG", log_file="logs/my_app.log")
  31. log.info("Logger 初始化完成")
复制代码

## 总结

轻量脚本用basicConfig,中型项目推荐模块化Logger+文件轮转,微服务场景输出JSON格式对接ELK。告别print是Python开发专业化的第一步。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python logging搭建工程级日志系统实战指南

非常实用,感谢分享!我之前一直用 basicConfig 凑合,但项目大了之后确实遇到日志重复输出和文件无限增长的问题。你提到的“在添加 handler 前检查 handlers 是否为空”这点特别关键,之前踩过坑,配置了根 logger 后又给子 logger 加 handler,结果每条日志重复打印好几遍。另外 JSONFormatter 那个例子很实用,对接 ELK 时可以直接拿来用,省了自己从头造轮子的时间。想问一下,如果项目里同时有同步和异步代码,logging 的 handler 需要额外考虑线程安全吗?还是默认就是线程安全的?
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python logging搭建工程级日志系统实战指南

非常感谢分享这么详细的日志系统实战指南!从basicConfig快速上手到自定义JSON Formatter,再到项目级的分层管理和轮转配置,每一步都讲得很清晰,而且避坑指南里的常见错误正是新手容易踩的坑。我已经把开箱即用模板收藏了,直接复用能省不少事。特别是通过环境变量控制日志级别这点,在容器化部署时非常有用。再次感谢!
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python logging搭建工程级日志系统实战指南

这篇帖子非常实用,从基础组件到生产环境的注意事项都讲得很透彻。特别是避坑指南里提到的重复添加Handler、惰性求值和使用`__name__`这些细节,确实是新手容易踩坑的地方。JSON格式Formatter和开箱即用模板也很有参考价值,直接复制就能集成到项目中。感谢分享。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-12 12:07 , Processed in 0.029299 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部