Python 日志(logging)全解析

文章目录

  • 一、核心概念(四大组件)
  • 二、日志级别
  • 三、基础使用
    • [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 手动配置流程

  1. 获取 Logger 实例(推荐用 name 作为日志器名,便于区分模块);
  2. 设置 Logger 级别;
  3. 创建 Handler(如控制台 Handler、文件 Handler),设置 Handler 级别;
  4. 创建 Formatter,绑定到 Handler;
  5. 将 Handler 添加到 Logger;
  6. (可选)添加 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 级别。

十一、最佳实践

  1. 统一配置 :项目入口处统一配置根日志器,子模块通过 name 获取 Logger,避免重复配置;
  2. 级别区分环境:开发环境用 DEBUG,测试 / 预发用 INFO,生产环境用 WARNING/ERROR;
  3. 日志格式规范:包含时间、级别、模块名、行号、消息,异常日志必须包含堆栈;
  4. 日志轮转:生产环境必须启用日志轮转(按大小 / 时间),避免日志文件过大;
  5. 敏感信息脱敏:日志中不记录密码、token、手机号等敏感信息;
  6. 结构化输出:生产环境优先使用 JSON 格式日志,便于日志分析;
  7. 避免使用 root Logger:尽量使用模块名 Logger,便于区分日志来源;
  8. 异常必记录:所有 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(灵活)或第三方库(便捷),确保日志可追溯、可分析、无敏感信息。

相关推荐
多米Domi0112 小时前
0x3f 第19天 javase黑马81-87 ,三更1-23 hot100子串
python·算法·leetcode·散列表
莫逸风2 小时前
【局域网服务方案】:无需找运营商,低成本拥有高性能服务器
运维·服务器
追风少年ii2 小时前
2025最后一天--解析依赖于空间位置的互作细胞亚群及下游功能效应
python·数据分析·空间·单细胞·培训
小鸡脚来咯3 小时前
python虚拟环境
开发语言·python
龘龍龙3 小时前
Python基础(九)
android·开发语言·python
小李独爱秋3 小时前
计算机网络经典问题透视:常规密钥体制与公钥体制最主要的区别是什么?—— 一文带你从“钥匙”看懂现代密码学核心
服务器·网络·tcp/ip·计算机网络·密码学
大学生毕业题目3 小时前
毕业项目推荐:91-基于yolov8/yolov5/yolo11的井盖破损检测识别(Python+卷积神经网络)
python·yolo·目标检测·cnn·pyqt·井盖破损
Amy_au3 小时前
Linux week 01
linux·运维·服务器
XLYcmy4 小时前
TarGuessIRefined密码生成器详细分析
开发语言·数据结构·python·网络安全·数据安全·源代码·口令安全