文章目录
- 一、核心概念(四大组件)
- 二、日志级别
- 三、基础使用
-
- [3.1 最简用法(默认配置)](#3.1 最简用法(默认配置))
- [3.2 基本配置(logging.basicConfig)](#3.2 基本配置(logging.basicConfig))
- 四、进阶配置(手动构建组件)
-
- [4.1 手动配置流程](#4.1 手动配置流程)
- [4.2 示例:多输出目标(控制台 + 文件)](#4.2 示例:多输出目标(控制台 + 文件))
- [4.3 过滤器(Filter)](#4.3 过滤器(Filter))
- 五、日志轮转(解决日志文件过大问题)
-
- [5.1 按大小轮转(RotatingFileHandler)](#5.1 按大小轮转(RotatingFileHandler))
- [5.2 按时间轮转(TimedRotatingFileHandler)](#5.2 按时间轮转(TimedRotatingFileHandler))
- 六、异常日志记录
-
- [6.1 logging.exception ()(推荐)](#6.1 logging.exception ()(推荐))
- [6.2 exc_info 参数](#6.2 exc_info 参数)
- 七、多模块日志管理
-
- [7.1 核心原则](#7.1 核心原则)
- [7.2 示例:多模块日志](#7.2 示例:多模块日志)
- [八、结构化日志(JSON 格式)](#八、结构化日志(JSON 格式))
-
- [8.1 自定义 Formatter 实现](#8.1 自定义 Formatter 实现)
- [8.2 第三方库(推荐)](#8.2 第三方库(推荐))
- 九、自定义扩展
-
- [9.1 自定义 Handler(如发送日志到钉钉 / 邮件)](#9.1 自定义 Handler(如发送日志到钉钉 / 邮件))
- [9.2 LoggerAdapter(添加上下文信息)](#9.2 LoggerAdapter(添加上下文信息))
- 十、常见问题与解决方案
-
- [10.1 日志不输出](#10.1 日志不输出)
- [10.2 日志重复输出](#10.2 日志重复输出)
- [10.3 中文乱码](#10.3 中文乱码)
- [10.4 级别不生效](#10.4 级别不生效)
- 十一、最佳实践
- 十二、第三方日志库(补充)
-
- [12.1 loguru(推荐)](#12.1 loguru(推荐))
- [12.2 structlog](#12.2 structlog)
- 总结
日志是程序运行状态、错误、关键操作的核心记录方式,相比 print 具有 可配置级别、持久化存储、多输出目标、结构化记录 等优势。Python 标准库 logging 是处理日志的核心工具,以下是其全量知识点。
一、核心概念(四大组件)
logging 模块的设计遵循模块化思想,核心由 4 个组件构成,组件间的关系:日志器(Logger)可添加多个处理器(Handler),每个处理器可绑定一个格式化器(Formatter)和多个过滤器(Filter)。
| 组件 | 作用 |
|---|---|
| Logger(日志器) | 程序直接调用的入口,负责生成日志记录(如 logger.debug()),可设置日志级别。 |
| Handler(处理器) | 决定日志的输出目标(控制台、文件、网络等),每个 Logger 可绑定多个 Handler。 |
| Formatter(格式化器) | 定义日志的输出格式(如包含时间、级别、模块名等)。 |
| Filter(过滤器) | 精细化控制日志的输出(如只允许特定模块 / 级别 / 关键词的日志通过)。 |
二、日志级别
日志级别用于区分日志的重要性,级别越高,记录的信息越关键。logging 定义了 5 个内置级别(可自定义),级别优先级:DEBUG < INFO < WARNING < ERROR < CRITICAL。
| 级别 | 数值 | 适用场景 |
|---|---|---|
| DEBUG | 10 | 调试信息(如变量值、函数执行步骤),仅开发 / 测试环境使用。 |
| INFO | 20 | 正常运行信息(如程序启动、关键操作完成),生产环境常用。 |
| WARNING | 30 | 警告信息(如配置缺失、资源不足,程序仍可运行)。 |
| ERROR | 40 | 错误信息(如函数调用失败、文件读取错误,部分功能受影响)。 |
| CRITICAL | 50 | 严重错误(如数据库连接失败、内存耗尽,程序无法继续运行)。 |
核心规则:只有日志的级别 ≥ 日志器 / 处理器设置的级别时,日志才会被输出。默认级别为 WARNING(即默认只输出 WARNING 及以上级别)。
三、基础使用
3.1 最简用法(默认配置)
直接调用 logging 模块的便捷方法,默认输出到控制台,格式为 级别:日志器名称:消息。
python
import logging
# 基础日志输出(默认级别WARNING)
logging.debug("调试信息:变量x=10") # 不输出(级别低于WARNING)
logging.info("程序启动成功") # 不输出
logging.warning("警告:配置文件未找到,使用默认配置") # 输出
logging.error("错误:读取文件失败") # 输出
logging.critical("严重错误:数据库连接中断") # 输出
3.2 基本配置(logging.basicConfig)
通过 basicConfig 快速配置日志(单例模式,仅第一次调用生效),支持设置级别、输出文件、格式等。
常用参数说明
| 参数 | 作用 |
|---|---|
| level | 设置日志器级别(如 logging.INFO)。 |
| filename | 日志输出到文件(指定路径),若不设置则输出到控制台。 |
| filemode | 文件打开模式:a(追加,默认)、w(覆盖)。 |
| format | 日志格式(支持占位符,见下文)。 |
| datefmt | 时间格式(如 %Y-%m-%d %H:%M:%S) |
| encoding | 文件编码(如 utf-8,解决中文乱码)。 |
| style | 格式字符串风格:%(默认)、{}、$。 |
示例:配置文件输出 + 自定义格式
python
import logging
# 基本配置(仅第一次调用生效)
logging.basicConfig(
level=logging.DEBUG, # 日志器级别设为DEBUG
filename="app.log", # 输出到文件
filemode="a", # 追加模式
encoding="utf-8", # 中文编码
format="%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(message)s", # 格式
datefmt="%Y-%m-%d %H:%M:%S" # 时间格式
)
# 输出日志
logging.debug("调试:用户ID=1001")
logging.info("信息:接口请求成功")
logging.warning("警告:磁盘空间不足80%")
logging.error("错误:接口返回码500")
logging.critical("严重:服务器内存耗尽")
日志格式占位符(常用)
| 占位符 | 含义 |
|---|---|
| %(asctime)s | 日志记录时间(人性化格式) |
| %(name)s | 日志器名称 |
| %(levelname)s | 日志级别名称(大写) |
| %(levelno)s | 日志级别数值 |
| %(lineno)d | 日志调用行号 |
| %(module)s | 日志所在模块名 |
| %(funcName)s | 日志所在函数名 |
| %(process)d | 进程 ID |
| %(thread)d | 线程 ID |
| %(message)s | 日志消息内容 |
四、进阶配置(手动构建组件)
basicConfig 仅适用于简单场景,复杂场景(多输出目标、自定义格式 / 过滤)需手动创建 Logger、Handler、Formatter。
4.1 手动配置流程
- 获取 Logger 实例(推荐用 name 作为日志器名,便于区分模块);
- 设置 Logger 级别;
- 创建 Handler(如控制台 Handler、文件 Handler),设置 Handler 级别;
- 创建 Formatter,绑定到 Handler;
- 将 Handler 添加到 Logger;
- (可选)添加 Filter 过滤日志。
4.2 示例:多输出目标(控制台 + 文件)
python
import logging
# 1. 获取Logger实例(模块名作为日志器名)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Logger级别(需≤Handler级别才会传递)
logger.propagate = False # 禁止向上传递到根日志器(避免重复输出)
# 2. 创建控制台Handler(StreamHandler)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # 控制台只输出INFO及以上
# 3. 创建文件Handler(FileHandler)
file_handler = logging.FileHandler("advanced.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG) # 文件输出DEBUG及以上
# 4. 创建格式化器
console_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
file_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(funcName)s - %(message)s")
# 5. 绑定格式化器到Handler
console_handler.setFormatter(console_formatter)
file_handler.setFormatter(file_formatter)
# 6. 添加Handler到Logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 测试日志
logger.debug("调试:数据库查询SQL=SELECT * FROM users") # 仅文件输出
logger.info("信息:用户登录成功,ID=1001") # 控制台+文件输出
logger.error("错误:数据库查询超时") # 控制台+文件输出
4.3 过滤器(Filter)
自定义 Filter 可精细化控制日志输出,例如:只允许特定模块的日志通过、过滤包含敏感信息的日志。
示例:过滤特定模块的日志
python
import logging
# 自定义过滤器:只允许日志器名包含"api"的日志通过
class ApiFilter(logging.Filter):
def filter(self, record):
# record是日志记录对象,包含name/levelno/message等属性
return "api" in record.name
# 获取Logger
logger = logging.getLogger("api.user")
logger.setLevel(logging.DEBUG)
# 创建Handler并添加过滤器
console_handler = logging.StreamHandler()
console_handler.addFilter(ApiFilter()) # 绑定过滤器
logger.addHandler(console_handler)
# 测试:日志器名包含"api",会输出
logger.info("API请求:/user/info")
# 另一个日志器(名不含api),不会输出
logger2 = logging.getLogger("db")
logger2.addHandler(console_handler)
logger2.info("数据库连接成功")
五、日志轮转(解决日志文件过大问题)
当日志文件持续写入时,文件体积会不断增大,logging.handlers 提供了轮转处理器,自动分割日志文件。
5.1 按大小轮转(RotatingFileHandler)
当文件达到指定大小后,自动创建新文件,保留指定数量的备份。
python
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 配置轮转处理器:单个文件最大10MB,最多保留5个备份
rotating_handler = RotatingFileHandler(
"app_rotating.log",
maxBytes=10*1024*1024, # 10MB
backupCount=5, # 保留5个备份
encoding="utf-8"
)
rotating_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(rotating_handler)
# 测试:循环输出日志,触发轮转
for i in range(10000):
logger.debug(f"测试轮转日志:{i}")
5.2 按时间轮转(TimedRotatingFileHandler)
按时间间隔(时 / 天 / 周)分割日志文件,适合按周期归档。
python
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 配置时间轮转:每天轮转,保留7天备份,后缀为时间格式
time_handler = TimedRotatingFileHandler(
"app_timed.log",
when="D", # 轮转单位:S(秒)/M(分)/H(时)/D(天)/W0(周一)/midnight(午夜)
interval=1, # 间隔1天
backupCount=7, # 保留7个备份
encoding="utf-8",
suffix="%Y-%m-%d.log" # 备份文件后缀(如app_timed.2025-12-29.log)
)
time_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(time_handler)
logger.info("按时间轮转的日志测试")
六、异常日志记录
记录异常信息是日志的核心场景,logging 提供了便捷方式:
6.1 logging.exception ()(推荐)
在 except 块中使用,自动记录异常堆栈信息,无需手动传参,级别为 ERROR。
python
import logging
logging.basicConfig(level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s")
try:
1 / 0 # 触发除零错误
except Exception:
# 自动记录堆栈信息
logging.exception("除零错误发生")
6.2 exc_info 参数
在 debug/info/error 等方法中设置 exc_info=True,手动记录异常堆栈。
python
try:
open("nonexist.txt", "r")
except FileNotFoundError as e:
logging.error("文件读取失败", exc_info=True) # 记录异常堆栈
七、多模块日志管理
大型项目通常分多个模块,需保证日志配置统一且可区分模块来源:
7.1 核心原则
- 每个模块通过 logging.getLogger(name ) 获取 Logger(name 为模块名,如 api.user);
- 根日志器(logging.getLogger())统一配置,子日志器继承根日志器的配置(无需重复配置);
- 避免重复添加 Handler(可通过判断 logger.handlers 是否为空)。
7.2 示例:多模块日志
python
# main.py(主模块)
import logging
import module_a
# 配置根日志器
def setup_logging():
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
# 避免重复添加Handler
if not root_logger.handlers:
console_handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
root_logger.addHandler(console_handler)
if __name__ == "__main__":
setup_logging()
logging.info("主程序启动")
module_a.do_something()
# module_a.py(子模块)
import logging
# 获取模块名对应的Logger
logger = logging.getLogger(__name__)
def do_something():
logger.debug("子模块调试信息")
logger.info("子模块执行操作")
八、结构化日志(JSON 格式)
默认日志为文本格式,不利于日志分析工具(如 ELK、Splunk)解析,可输出 JSON 格式日志:
8.1 自定义 Formatter 实现
python
import logging
import json
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record):
# 构造JSON日志字段
log_data = {
"time": datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S"),
"logger": record.name,
"level": record.levelname,
"message": record.getMessage(),
"lineno": record.lineno
}
# 若有异常,添加堆栈信息
if record.exc_info:
log_data["exc_info"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
# 配置Logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
# 测试
logger.info("用户登录", extra={"user_id": 1001}) # extra可添加自定义字段
logger.error("接口调用失败", exc_info=True)
8.2 第三方库(推荐)
使用 python-json-logger 简化配置:
bash
pip install python-json-logger
python
import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("结构化日志测试", extra={"request_id": "abc123"})
九、自定义扩展
9.1 自定义 Handler(如发送日志到钉钉 / 邮件)
python
import logging
import requests
# 自定义Handler:发送ERROR级别日志到钉钉群
class DingTalkHandler(logging.Handler):
def __init__(self, webhook_url):
super().__init__()
self.webhook_url = webhook_url
self.setLevel(logging.ERROR) # 只处理ERROR及以上级别
def emit(self, record):
# 格式化日志消息
log_msg = self.format(record)
# 发送钉钉消息
data = {
"msgtype": "text",
"text": {"content": f"【系统错误】\n{log_msg}"}
}
requests.post(self.webhook_url, json=data)
# 配置
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(console_handler)
# 钉钉Handler
ding_handler = DingTalkHandler("https://oapi.dingtalk.com/robot/send?access_token=你的token")
ding_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(message)s"))
logger.addHandler(ding_handler)
# 测试:ERROR级别日志会发送到钉钉
logger.error("数据库连接失败,系统无法运行")
9.2 LoggerAdapter(添加上下文信息)
为日志添加全局上下文(如用户 ID、请求 ID),无需每次手动传 extra:
python
import logging
# 自定义LoggerAdapter
class ContextLoggerAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
# 添加上下文信息
return f"[用户ID:{self.extra['user_id']}] {msg}", kwargs
# 使用
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler())
# 绑定上下文
adapter = ContextLoggerAdapter(logger, {"user_id": 1001})
adapter.info("登录成功")
adapter.error("下单失败")
十、常见问题与解决方案
10.1 日志不输出
- 原因 1:日志器 / 处理器级别设置过高(如 Logger 级别为 INFO,调用 debug ());
- 原因 2:basicConfig 调用时机晚于第一次日志调用(basicConfig 仅第一次生效);
- 原因 3:Logger 没有绑定 Handler(如手动创建 Logger 但未添加 Handler);
- 解决方案:检查级别配置、确保 basicConfig 先调用、为 Logger 添加 Handler。
10.2 日志重复输出
- 原因 1:多次添加同一个 Handler(如配置函数被多次调用);
- 原因 2:子日志器向上传递到根日志器,根日志器和子日志器都有 Handler;
- 解决方案:
python
# 方案1:判断Handler是否为空再添加
if not logger.handlers:
logger.addHandler(handler)
# 方案2:禁止子日志器向上传递
logger.propagate = False
10.3 中文乱码
- 原因:FileHandler 未指定编码为 utf-8;
- 解决方案:
python
logging.FileHandler("app.log", encoding="utf-8")
10.4 级别不生效
- 原因:Logger 和 Handler 都有级别,取更严格的(如 Logger 级别 DEBUG,Handler 级别 WARNING,最终只输出 WARNING+);
- 解决方案:确保 Handler 级别 ≤ Logger 级别。
十一、最佳实践
- 统一配置 :项目入口处统一配置根日志器,子模块通过 name 获取 Logger,避免重复配置;
- 级别区分环境:开发环境用 DEBUG,测试 / 预发用 INFO,生产环境用 WARNING/ERROR;
- 日志格式规范:包含时间、级别、模块名、行号、消息,异常日志必须包含堆栈;
- 日志轮转:生产环境必须启用日志轮转(按大小 / 时间),避免日志文件过大;
- 敏感信息脱敏:日志中不记录密码、token、手机号等敏感信息;
- 结构化输出:生产环境优先使用 JSON 格式日志,便于日志分析;
- 避免使用 root Logger:尽量使用模块名 Logger,便于区分日志来源;
- 异常必记录:所有 except 块必须记录异常日志(使用 exception() 或 exc_info=True)。
十二、第三方日志库(补充)
原生 logging 配置稍繁琐,可使用第三方库简化:
12.1 loguru(推荐)
开箱即用,无需复杂配置,支持自动轮转、结构化、异常捕获等:
bash
pip install loguru
python
from loguru import logger
# 基本使用
logger.debug("调试信息")
logger.info("正常信息")
logger.error("错误信息", exc_info=True)
# 配置文件轮转
logger.add("app.log", rotation="10 MB", retention="7 days", encoding="utf-8")
12.2 structlog
专注结构化日志,与 logging 兼容,支持多处理器、上下文绑定:
bash
pip install structlog
总结
Python 日志的核心是理解 Logger/Handler/Formatter/Filter 四大组件的协作关系,掌握级别控制、多输出目标、日志轮转、异常记录等核心功能。生产环境中需结合项目特点,选择原生 logging(灵活)或第三方库(便捷),确保日志可追溯、可分析、无敏感信息。