很多Python开发者习惯用print调试,但到了生产环境,print无法控制日志级别、持久化和格式化,排查问题非常困难。logging模块是Python标准库提供的日志系统,支持分级输出、文件记录和格式自定义,是替代print的正确选择。
## 快速入门:五大组件
logging模块由五个核心组件构成:Logger(日志入口,负责分级记录)、Handler(输出目的地,如文件或终端)、Formatter(定义日志格式)、Filter(按条件过滤日志)、Level(日志级别,从低到高为DEBUG、INFO、WARNING、ERROR、CRITICAL)。理解这些组件有助于灵活配置日志系统。
## 一行配置即插即用
对于小型项目,使用basicConfig快速完成配置,同时输出到终端和文件:- import logging
- logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
- datefmt="%Y-%m-%d %H:%M:%S",
- handlers=[
- logging.StreamHandler(),
- logging.FileHandler("app.log", encoding="utf-8"),
- ],
- )
- log = logging.getLogger(__name__)
- log.info("服务启动成功")
- log.warning("磁盘使用率超过 80%%")
- log.error("数据库连接超时", exc_info=True) # 自动打印堆栈
复制代码 注意:exc_info=True可以在ERROR级别自动输出异常堆栈,便于定位问题。
## 日志轮转防止磁盘撑爆
文件日志无限增长会耗尽磁盘空间。使用RotatingFileHandler按大小自动切割:- from logging.handlers import RotatingFileHandler
- handler = RotatingFileHandler(
- "app.log",
- maxBytes=10 * 1024 * 1024, # 10MB
- backupCount=5, # 保留5个备份
- encoding="utf-8",
- )
- handler.setFormatter(logging.Formatter(
- "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
- ))
复制代码 当文件达到10MB时,handler自动重命名并创建新文件,保留最近5个历史备份。
## 按模块分层管理
大型项目不应使用单一全局Logger,应为每个模块创建独立Logger,方便定位日志来源:- # utils/db.py
- logger = logging.getLogger("app.db")
- # utils/http.py
- logger = logging.getLogger("app.http")
复制代码 根Logger(如"app")统一配置后,子Logger自动继承,无需重复设置Handler和格式。
## 高级:输出JSON格式日志
对接ELK等日志平台时,JSON格式更易解析。自定义Formatter将日志转换为JSON字符串:- import json
- import logging
- class JsonFormatter(logging.Formatter):
- def format(self, record: logging.LogRecord) -> str:
- log_obj = {
- "time": self.formatTime(record, self.datefmt),
- "level": record.levelname,
- "logger": record.name,
- "message": record.getMessage(),
- }
- if record.exc_info and record.exc_info[0]:
- log_obj["exception"] = self.formatException(record.exc_info)
- 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及以上)和文件(含轮转)的完整配置,适合直接集成到项目中:- import logging
- import logging.handlers
- import sys
- from pathlib import Path
- def setup_logger(
- name: str = __name__,
- level: str = "INFO",
- log_file: str | None = None,
- ) -> logging.Logger:
- logger = logging.getLogger(name)
- if logger.handlers:
- return logger
- logger.setLevel(getattr(logging, level.upper(), logging.INFO))
- fmt = logging.Formatter(
- "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d: %(message)s"
- )
- console = logging.StreamHandler(sys.stdout)
- console.setLevel(logging.WARNING)
- console.setFormatter(fmt)
- logger.addHandler(console)
- if log_file:
- Path(log_file).parent.mkdir(parents=True, exist_ok=True)
- handler = logging.handlers.RotatingFileHandler(
- log_file, maxBytes=10 << 20, backupCount=5, encoding="utf-8"
- )
- handler.setLevel(logging.DEBUG)
- handler.setFormatter(fmt)
- logger.addHandler(handler)
- return logger
- log = setup_logger("my_app", level="DEBUG", log_file="logs/my_app.log")
- log.info("Logger 初始化完成")
复制代码
## 总结
轻量脚本用basicConfig,中型项目推荐模块化Logger+文件轮转,微服务场景输出JSON格式对接ELK。告别print是Python开发专业化的第一步。 |