Python日志模块logging的新手入门教程
作者:程序员食堂
一、为什么不能用 print 代替日志?
很多新手刚学 Python 时,调试代码全靠 print():
print("用户登录成功")
print("数据库连接失败")
print("订单处理完成")代码量少时没问题,但项目一复杂,print 的缺陷就暴露了:
| 问题 | logging | |
|---|---|---|
| 无法分级 | 所有信息混在一起 | 五级日志,按需过滤 |
| 无法持久化 | 程序结束就没了 | 自动写入文件保存 |
| 无法控制输出 | 只能打印到控制台 | 控制台/文件/邮件/网络随意切换 |
| 性能差 | 无条件执行字符串拼接 | 级别不够时直接跳过,零开销 |
| 无上下文 | 纯字符串,不知道从哪来的 | 时间/模块/行号/级别全记录 |
| 多线程不安全 | 输出可能交错混乱 | 内置锁机制,线程安全 |
一句话总结:print 是往屏幕上吐字符串,logging 是一条完整的"日志生产线"——从产生日志到最终落地,每一步都可控。
二、logging 模块的四大核心组件
Python 的 logging 模块采用分层解耦架构,核心由四大组件构成:
你的代码:logger.info("...")
│
▼
┌───────────────┐ ① Logger:日志入口,级别过滤的第一道关卡
│ Logger │ getLogger('app.api'),命名空间支持层级继承
└───────┬───────┘
│
▼
┌───────────────┐ ② Filter:比级别更细的过滤,也可给 record 注入字段
│ Filter │ addFilter()
└───────┬───────┘
│
▼
┌───────────────┐ ③ Handler:决定日志"去哪",级别过滤的第二道关卡
│ Handler │ Stream / File / Rotating / SMTP / Socket / Queue
└───────┬───────┘
│
▼
┌───────────────┐ ④ Formatter:决定日志"长什么样"
│ Formatter │ %(asctime)s %(name)s %(levelname)s %(message)s
└───────┬───────┘
│
▼
控制台 / 文件 / 网络 / 邮件 / 消息队列
1. Logger(日志器)—— 入口和控制中心
Logger 是你直接打交道的对象,主要职责包括:
- 接收日志调用:提供
debug()、info()、warning()、error()、critical()等方法 - 级别过滤:判断日志是否达到设定阈值,决定是否继续处理
- 层级管理:支持以点号分隔的命名空间(如
app.db.query),子 Logger 自动继承父 Logger 配置 - 分发日志:将日志消息传递给绑定的 Handler 进行输出
import logging # 推荐用 __name__ 作为名称,自动形成层级结构 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # 设置最低日志级别
2. Handler(处理器)—— 决定日志去哪
Handler 决定日志的输出目标,一个 Logger 可以绑定多个 Handler:
| Handler | 用途 |
|---|---|
StreamHandler | 输出到控制台 |
FileHandler | 输出到文件 |
RotatingFileHandler | 按文件大小轮转,防止日志撑爆磁盘 |
TimedRotatingFileHandler | 按时间轮转(每天/每小时) |
SMTPHandler | 发送邮件通知 |
HTTPHandler | 发送到远程服务器 |
QueueHandler | 异步队列处理,适合高并发场景 |
NullHandler | 库开发用,避免未配置时输出到 stderr |
3. Formatter(格式化器)—— 决定日志长什么样
Formatter 定义日志的输出格式,通过占位符配置:
| 占位符 | 含义 |
|---|---|
%(asctime)s | 日志产生时间 |
%(name)s | 日志器名称 |
%(levelname)s | 日志级别名称 |
%(message)s | 日志内容 |
%(filename)s | 产生日志的文件名 |
%(lineno)d | 产生日志的代码行号 |
%(funcName)s | 产生日志的函数名 |
4. Filter(过滤器)—— 精细化筛选
Filter 用于比级别更细粒度的过滤,可基于名称、自定义条件等进行筛选,通过 addFilter() 挂载到 Logger 或 Handler 上。
三、五种标准日志级别
Logger 定义了五个标准级别,数值越大越严重:
| 级别 | 数值 | 用途 | 开发环境 | 生产环境 |
|---|---|---|---|---|
DEBUG | 10 | 调试细节,记录变量值、执行流程 | 开启 | 关闭 |
INFO | 20 | 正常流程,确认程序按预期运行 | 开启 | 开启 |
WARNING | 30 | 潜在问题,但不影响运行 | 开启 | 开启 |
ERROR | 40 | 发生错误,功能受影响 | 开启 | 开启 |
CRITICAL | 50 | 严重故障,可能导致程序崩溃 | 开启 | 开启 |
默认级别为 WARNING,只有 ≥ 设定级别的日志才会被处理。
四、Logger 的内部执行流程
当调用 logger.info("msg") 时,内部流程如下:
调用 Logger 方法
↓
创建 LogRecord 对象
↓
Logger 级别过滤(是否 ≥ 设定级别?)
↓
Filter 过滤(自定义筛选条件)
↓
分发至所有绑定的 Handler
↓
Handler 级别过滤 → Formatter 格式化 → emit() 输出
关键规则:日志级别需同时 ≥ Logger 级别和 Handler 级别时才会输出。
五、从入门到进阶:三种配置方式
方式一:基础配置(适合简单脚本)
使用 logging.basicConfig() 一行搞定:
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
filename="app.log", # 输出到文件
filemode="a", # 追加模式
encoding="utf-8" # 避免中文乱码
)
logger = logging.getLogger(__name__)
logger.info("服务启动成功")️ basicConfig() 仅在首次调用时生效,第二次调用无效。
方式二:手动组装(适合正式项目)
显式创建 Logger、Handler 和 Formatter,灵活组合:
import logging
def setup_logger():
# 1. 创建 Logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.propagate = False # 禁止向上传播,避免重复输出
# 2. 创建控制台 Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台只输出 INFO 及以上
# 3. 创建文件 Handler
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG) # 文件记录所有级别
# 4. 创建 Formatter
formatter = logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(name)s | %(filename)s:%(lineno)d | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# 5. 绑定 Formatter 到 Handler
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 6. 绑定 Handler 到 Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 使用
logger = setup_logger()
logger.debug("调试信息")
logger.info("正常信息")
logger.error("错误信息", exc_info=True) # 记录完整异常堆栈方式三:字典配置(适合大型项目)
通过 logging.config.dictConfig() 加载外部配置,支持 JSON/YAML 格式:
import logging.config
config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.FileHandler",
"level": "INFO",
"formatter": "standard",
"filename": "app.log",
"encoding": "utf8"
}
},
"loggers": {
"my_app": {
"level": "DEBUG",
"handlers": ["console", "file"],
"propagate": False
}
}
}
logging.config.dictConfig(config)
logger = logging.getLogger("my_app")六、常用方法速查
| 方法 | 说明 |
|---|---|
logger.debug(msg) | 记录 DEBUG 级别日志 |
logger.info(msg) | 记录 INFO 级别日志 |
logger.warning(msg) | 记录 WARNING 级别日志 |
logger.error(msg) | 记录 ERROR 级别日志 |
logger.critical(msg) | 记录 CRITICAL 级别日志 |
logger.exception(msg) | 记录 ERROR 级别 + 完整异常堆栈(等价于 error(msg, exc_info=True)) |
logger.log(level, msg) | 动态指定日志级别 |
记录变量的正确写法(使用 %s 占位符,而非 f-string):
# 推荐:延迟格式化,性能更好
logger.info("用户 %s 登录成功,ID: %d", username, user_id)
# 不推荐:f-string 会无条件计算
logger.info(f"用户 {username} 登录成功,ID: {user_id}")七、日志轮转:避免日志文件撑爆磁盘
当程序长期运行时,单一日志文件会越来越大。logging.handlers 提供了两种轮转机制:
按文件大小轮转(RotatingFileHandler)
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
"app.log",
maxBytes=10 * 1024 * 1024, # 单个文件最大 10MB
backupCount=5, # 保留最近 5 个历史文件
encoding="utf-8"
)按时间轮转(TimedRotatingFileHandler)
from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
"app.log",
when="D", # 轮转单位:S(秒)、M(分)、H(时)、D(天)、midnight(午夜)
interval=1, # 间隔时间
backupCount=7, # 保留最近 7 天日志
encoding="utf-8"
)八、新手常见坑点
| 坑点 | 原因 | 解决方案 |
|---|---|---|
| debug 日志不输出 | 默认级别为 WARNING | 手动调用 logger.setLevel(logging.DEBUG) |
| 日志重复打印 | 多次 addHandler 或 propagate 未关闭 | 调用 logger.handlers.clear() 或设置 propagate=False |
| 日志文件乱码 | 未指定编码 | FileHandler 中指定 encoding="utf-8" |
| 设置级别无效 | 只给 Logger 设了级别,没给 Handler 设 | Logger 和 Handler 都要设置级别 |
| f-string 性能差 | 即使级别不够也会执行字符串拼接 | 改用 %s 占位符 |
九、最佳实践总结
用 __name__ 命名 Logger:便于追踪日志来源,自动形成层级结构
避免重复配置:不要在每个模块调用 basicConfig(),应在应用入口集中配置
库开发加 NullHandler:避免未配置时日志输出到 stderr
生产环境慎用 DEBUG:高频路径输出 DEBUG 日志会影响性能
使用 exc_info=True 或 exception():记录完整异常堆栈,是排障的关键信息
用 %s 占位符而非 f-string:避免不必要的性能开销
设置 propagate=False:避免日志被传递到 root Logger,导致重复输出
十、完整生产级示例
import logging
import logging.handlers
import json
from pathlib import Path
class JsonFormatter(logging.Formatter):
"""JSON 格式化器,便于 ELK/Loki 采集分析"""
def format(self, record):
log_data = {
"time": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"line": record.lineno,
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
def setup_production_logging(log_dir="logs"):
"""配置生产级日志体系"""
log_path = Path(log_dir)
log_path.mkdir(exist_ok=True)
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
# 统一格式
fmt = logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# 控制台 Handler:只输出 WARNING 及以上
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(fmt)
# 文件 Handler:输出所有级别,按大小轮转
file_handler = logging.handlers.RotatingFileHandler(
log_path / "app.log",
maxBytes=10 * 1024 * 1024, # 10MB 轮转
backupCount=5,
encoding="utf-8",
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(fmt)
# 错误专用 Handler
error_handler = logging.handlers.RotatingFileHandler(
log_path / "error.log",
maxBytes=5 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(fmt)
root_logger.addHandler(console)
root_logger.addHandler(file_handler)
root_logger.addHandler(error_handler)
# 使用
setup_production_logging()
logger = logging.getLogger(__name__)
logger.info("应用启动")
logger.error("请求失败", exc_info=True)核心思想:Logger 负责"记录什么",Handler 负责"输出到哪",Formatter 负责"长什么样"。这种解耦设计让日志系统既灵活又可扩展。掌握这套体系,你就从"用 print 调试"的新手,迈向了"用 logging 构建可观测系统"的工程师。
以上就是Python日志模块logging的新手入门教程的详细内容,更多关于Python logging模块的资料请关注脚本之家其它相关文章!
