发布于2026-07-13 阅读(0)
扫一扫,手机访问
在 Python 开发中,日志系统的重要性不言而喻。但很多开发者,尤其是刚入门的同学,习惯了用print一把梭——调试时满屏输出,上线后却两眼一抹黑。生产环境下的日志,关键在于分级、持久化、格式化控制,而这些恰恰是print给不了的。那么,该怎么搭建一套像样的日志系统呢?

Python 标准库的 logging 模块,本质上是一套组件化的日志框架。理解它的五大组件,是掌握日志系统的第一步。
| 组件 | 职责 |
|---|---|
| Logger | 日志入口,分级控制 |
| Handler | 输出目的地,比如文件、终端、网络 |
| Formatter | 格式化输出样式 |
| Filter | 按条件过滤日志 |
| Level | 日志级别:DEBUG → INFO → WARNING → ERROR → CRITICAL |
简单来说,Logger 负责发出消息,Handler 决定把消息送到哪里,Formatter 决定消息长什么样,Filter 决定消息能不能发出去,Level 则决定消息的优先级。
如果你只是想快速用起来,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 这个参数很实用,当发生异常时,它会自动把完整的堆栈信息打印出来,省去了手动 traceback 的麻烦。
日志文件无限增长,迟早会撑爆磁盘。这时候,RotatingFileHandler 就派上用场了:
复制代码from logging.handlers import RotatingFileHandlerhandler = 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 时,会自动切割,并保留最近 5 个备份。这样既不会丢失历史日志,也不会让磁盘空间持续告急。
在大型项目中,千万不要全局一把梭。每个模块应该拥有独立的 Logger:
复制代码# utils/db.py
logger = logging.getLogger("app.db")# utils/http.py
logger = logging.getLogger("app.http")
配合 logging.getLogger("app") 统一配置,子 Logger 会自动继承父配置,省心省力。这种方式的好处是,当某个模块出问题时,你能快速定位到是哪个模块的日志,而不必在一堆混杂的输出中大海捞针。
如果项目规模再大一些,需要对接 ELK、Loki 等日志收集平台,那 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)
| 坑 | 正确做法 |
|---|---|
logger = logging.getLogger() 空参 | 用 __name__,方便定位模块 |
f"user={user}" 占位符用 f-string | 用 "user=%s" % user 惰性求值,避免不必要开销 |
重复添加 Handler(多次调用 basicConfig) | 使用 not logger.handlers 判空,或用单例 |
| 中文乱码 | FileHandler 加上 encoding="utf-8" |
| 生产环境打印 DEBUG 日志 | 用环境变量控制级别:level=getattr(logging, os.getenv("LOG_LEVEL", "INFO")) |
这里特别想提一下占位符的问题。很多开发者习惯用 f-string 来拼接日志字符串,比如 f"user={user}"。但这样做的问题是,无论日志级别如何,都会先进行字符串格式化,浪费性能。而 "user=%s" % user 这种写法,只有在日志级别被触发时,才会进行格式化,这就是惰性求值的优势。
说了这么多,最后直接给一个可以直接复制使用的配置函数:
复制代码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"
) # 终端 Handler(始终输出 >= WARNING)
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.WARNING)
console.setFormatter(fmt)
logger.addHandler(console) # 文件 Handler 带轮转(输出全部级别)
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 初始化完成")
logging.basicConfig 一行搞定从现在开始,所有 Python 项目都值得用 logging 替代 print,这是专业开发者与新手的分水岭。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8