Python 日志(logging)全解析
文章目录
- 一、核心概念(四大组件)
- 二、日志级别
- 三、基础使用
- 四、进阶配置(手动构建组件)
- 五、日志轮转(解决日志文件过大问题)
- 六、异常日志记录
- 七、多模块日志管理
- 八、结构化日志(JSON 格式)
- 九、自定义扩展
- 十、常见问题与解决方案
- 十一、最佳实践
- 十二、第三方日志库(补充)
- 总结
日志是程序运行状态、错误、关键操作的核心记录方式,相比 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 模块的便捷方法,默认输出到控制台,格式为 级别:日志器名称:消息。
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 | 格式字符串风格:%(默认)、{}、$。 |
示例:配置文件输出 + 自定义格式
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 示例:多输出目标(控制台 + 文件)
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 可精细化控制日志输出,例如:只允许特定模块的日志通过、过滤包含敏感信息的日志。
示例:过滤特定模块的日志
import logging # 自定义过滤器:只允许日志器名包含"api"的日志通过classApiFilter(logging.Filter):deffilter(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)
当文件达到指定大小后,自动创建新文件,保留指定数量的备份。
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 inrange(10000): logger.debug(f"测试轮转日志:{i}")5.2 按时间轮转(TimedRotatingFileHandler)
按时间间隔(时 / 天 / 周)分割日志文件,适合按周期归档。
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。
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,手动记录异常堆栈。
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 示例:多模块日志
# main.py(主模块)import logging import module_a # 配置根日志器defsetup_logging(): root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG)# 避免重复添加Handlerifnot 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__)defdo_something(): logger.debug("子模块调试信息") logger.info("子模块执行操作")八、结构化日志(JSON 格式)
默认日志为文本格式,不利于日志分析工具(如 ELK、Splunk)解析,可输出 JSON 格式日志:
8.1 自定义 Formatter 实现
import logging import json from datetime import datetime classJsonFormatter(logging.Formatter):defformat(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 简化配置:
pip install python-json-logger 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(如发送日志到钉钉 / 邮件)
import logging import requests # 自定义Handler:发送ERROR级别日志到钉钉群classDingTalkHandler(logging.Handler):def__init__(self, webhook_url):super().__init__() self.webhook_url = webhook_url self.setLevel(logging.ERROR)# 只处理ERROR及以上级别defemit(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:
import logging # 自定义LoggerAdapterclassContextLoggerAdapter(logging.LoggerAdapter):defprocess(self, msg, kwargs):# 添加上下文信息returnf"[用户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;
- 解决方案:
# 方案1:判断Handler是否为空再添加ifnot logger.handlers: logger.addHandler(handler)# 方案2:禁止子日志器向上传递 logger.propagate =False10.3 中文乱码
- 原因:FileHandler 未指定编码为 utf-8;
- 解决方案:
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(推荐)
开箱即用,无需复杂配置,支持自动轮转、结构化、异常捕获等:
pip install loguru 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 兼容,支持多处理器、上下文绑定:
pip install structlog 总结
Python 日志的核心是理解 Logger/Handler/Formatter/Filter 四大组件的协作关系,掌握级别控制、多输出目标、日志轮转、异常记录等核心功能。生产环境中需结合项目特点,选择原生 logging(灵活)或第三方库(便捷),确保日志可追溯、可分析、无敏感信息。