大多数 Python 入门教程都用 print 调试,但到了真实项目里,print 会带来很多问题:没有时间戳、没有级别区分、无法控制输出目标。logging 模块就是为了解决这些问题。
一、为什么不能只靠 print
# 问题 1:不知道什么时候发生的
print("开始处理")
# 问题 2:没有级别区分,调试信息和错误信息混在一起
print("用户数据:", user_data)
print("出错了!")
# 问题 3:无法关闭,上线后仍然会输出
# 问题 4:无法同时写入文件和终端
二、logging 最简单的用法
import logging
# 配置基础日志级别为 INFO,低于 INFO 的日志(如 DEBUG)将被忽略
logging.basicConfig(level=logging.INFO)
# 记录不同级别的日志信息
logging.info("程序启动")
logging.warning("配置文件缺失,使用默认值")
logging.error("数据库连接失败")
输出效果:
INFO:root:程序启动
WARNING:root:配置文件缺失,使用默认值
ERROR:root:数据库连接失败
三、日志级别
从低到高排列:
| 级别 | 用途 | 何时使用 |
|---|---|---|
DEBUG |
详细调试信息 | 开发阶段排查问题 |
INFO |
确认程序按预期运行 | 关键流程节点 |
WARNING |
意外情况,但程序仍可运行 | 配置回退、非致命异常 |
ERROR |
严重问题,功能受影响 | 请求失败、数据库异常 |
CRITICAL |
程序可能无法继续运行 | 系统崩溃、核心服务不可用 |
# 记录调试信息,通常用于开发阶段
logging.debug("变量 x = %s", x)
# 记录常规运行信息
logging.info("用户 %s 登录成功", username)
# 记录警告信息,程序仍可继续运行
logging.warning("磁盘使用率 %d%%", usage_percent)
# 记录错误信息,部分功能可能受影响
logging.error("接口调用失败: %s", error_msg)
# 记录严重错误,程序可能即将崩溃
logging.critical("主进程异常退出")
实际经验:
- 生产环境通常只输出
WARNING及以上 DEBUG日志只在排查问题时临时开启INFO用于记录关键业务节点
四、自定义日志格式
import logging
# 配置日志级别、输出格式和时间格式
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s", # 自定义日志格式
datefmt="%Y-%m-%d %H:%M:%S", # 自定义时间格式
)
# 记录一条 INFO 级别的日志
logging.info("用户登录")
输出:
2026-06-20 10:30:00 [INFO] root - 用户登录
常用格式占位符
| 占位符 | 含义 |
|---|---|
%(asctime)s |
时间戳 |
%(levelname)s |
日志级别 |
%(name)s |
Logger 名称 |
%(message)s |
日志消息 |
%(filename)s |
文件名 |
%(lineno)d |
行号 |
%(funcName)s |
函数名 |
五、使用 Logger 对象而不是 root
import logging
# 创建模块级别的 logger
logger = logging.getLogger(__name__)
def process_order(order_id):
logger.info("开始处理订单 %s", order_id)
try:
# 业务逻辑
logger.info("订单 %s 处理完成", order_id)
except Exception as e:
logger.error("订单 %s 处理失败: %s", order_id, e)
raise
好处:
__name__自动使用模块名作为 logger 名称- 可以按模块或功能区设置不同的日志级别
- 日志输出中能清楚看到来源模块
六、Handler:日志输出到哪里
Handler 决定日志写到终端、文件还是网络。一个 logger 可以同时挂多个 handler。
import logging
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# 输出到终端
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 输出到文件
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
# 统一格式
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s - %(message)s")
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.info("这条消息会同时出现在终端和文件中")
logger.debug("这条消息只会出现在文件中")
七、日志文件轮转
日志文件不能无限增长,需要用 RotatingFileHandler 做轮转。
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# 单个文件最大 5MB,最多保留 5 个备份
handler = RotatingFileHandler(
"app.log",
maxBytes=5 * 1024 * 1024,
backupCount=5,
encoding="utf-8",
)
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(handler)
还有按时间轮转的 TimedRotatingFileHandler:
from logging.handlers import TimedRotatingFileHandler
# 每天轮转一次,保留 30 天
handler = TimedRotatingFileHandler(
"app.log",
when="midnight",
interval=1,
backupCount=30,
encoding="utf-8",
)
八、字典配置:更灵活的方式
import logging.config
# 定义日志配置字典
LOGGING = {
"version": 1,
"disable_existing_loggers": False, # 是否禁用已存在的 logger
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s - %(message)s"
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler", # 输出到控制台
"level": "INFO",
"formatter": "standard",
},
"file": {
"class": "logging.handlers.RotatingFileHandler", # 输出到文件并支持轮转
"filename": "app.log",
"maxBytes": 5242880, # 5MB
"backupCount": 5,
"encoding": "utf-8",
"level": "DEBUG",
"formatter": "standard",
},
},
"root": {
"level": "DEBUG",
"handlers": ["console", "file"], # 绑定 handler
},
}
# 应用字典配置
logging.config.dictConfig(LOGGING)
这套字典配置不只适用于普通 Python 项目,在 Django 里也经常直接放到 settings.py 的 LOGGING 变量中。
九、实战建议
- 不要在日志中打印敏感信息(密码、token、身份证号)
- 日志消息要有足够的上下文(用户ID、订单号、请求路径)
- 使用
%s格式化而不是 f-string,避免不必要的字符串拼接开销
# 好:延迟格式化,只有实际输出时才拼接字符串
logger.info("用户 %s 发起订单 %s", user_id, order_id)
# 不好:即使日志级别不输出,f-string 也会执行拼接
logger.info(f"用户 {user_id} 发起订单 {order_id}")
- 异常日志要带上 traceback:
try:
risky_operation()
except Exception:
logger.exception("操作失败") # 自动记录完整堆栈
小结
从 print 到 logging 的关键跃迁:
- 用级别区分日志重要性
- 用 logger 对象代替 root logger
- 用 handler 控制输出目标
- 用文件轮转控制日志大小
- 用字典配置统一管理
掌握这些后,你的代码就具备了生产级的可观测性。
相关阅读
- 《Python 输入输出与异常》:先熟悉文件、
with与try / except - 《Python 函数与模块》:先熟悉函数、模块与类型提示,让日志工具函数更清晰
- 《Python 网络与并发》:服务端程序和并发任务尤其依赖可观测性