侧边栏壁纸
博主头像
学习笔记

行动起来,活在当下

  • 累计撰写 9 篇文章
  • 累计创建 2 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

将 Python 标准库 logging 重定向到 Loguru

brian
2025-09-22 / 0 评论 / 0 点赞 / 38 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

为什么选择 Loguru?

Python 内置的 logging 模块虽然功能完整,但配置繁琐、使用复杂,往往需要大量样板代码。Loguru 是一个让日志处理重新变得简单的第三方库,相比标准库有以下显著优势:

  1. 开箱即用:无需任何配置,导入即用,默认就有漂亮的彩色输出

  2. 异常追踪:自动记录异常的完整堆栈信息,变量值一目了然

  3. 功能强大:支持日志轮转、异步记录、多目标输出、上下文绑定等

  4. 无缝集成:可通过 InterceptHandler 接管标准库 logging,与第三方库完全兼容

1. 安装

使用 pip:

pip install loguru

或使用 uv(推荐):

uv add loguru

2. 基础用法

Loguru 默认就能用,无需任何初始化配置:

from loguru import logger

logger.debug("这是一条调试信息")
logger.info("这是一条普通信息")
logger.warning("这是一条警告信息")
logger.error("这是一条错误信息")
logger.critical("这是一条严重错误信息")
logger.success("操作成功!")  # Loguru 独有的 success 级别

3. 异常处理

Loguru 对异常的支持非常强大,能自动打印完整堆栈和变量值:

from loguru import logger

# 方式一:手动记录异常
try:
    1 / 0
except Exception:
    logger.exception("发生了除零错误")  # 自动附加堆栈信息

# 方式二:装饰器自动捕获
@logger.catch
def risky_function():
    return 1 / 0

risky_function()  # 异常被自动捕获并记录,程序不会崩溃

4. 日志文件与轮转

Loguru 的文件输出配置非常直观,支持按大小、时间自动轮转:

from loguru import logger
import sys

# 移除默认的控制台输出(可选)
logger.remove()

# 重新添加控制台输出,设置级别
logger.add(sys.stderr, level="INFO")

# 添加文件输出,配置轮转和压缩
logger.add(
    "logs/app_{time:YYYY-MM-DD}.log",  # 按日期命名
    rotation="10 MB",       # 超过 10MB 自动切换
    retention="7 days",     # 保留最近 7 天
    compression="zip",      # 压缩历史日志
    level="DEBUG",
    encoding="utf-8",
    format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{line} | {message}"
)

5. 格式化与上下文绑定

Loguru 支持结构化日志和上下文信息绑定,非常适合追踪请求链路:

from loguru import logger

# 结构化字段
logger.info("用户 {name} 登录成功,IP: {ip}", name="张三", ip="192.168.1.1")

# 持久绑定上下文(适合同一用户/请求的多条日志)
user_logger = logger.bind(user_id="u001", username="张三")
user_logger.info("开始执行操作")
user_logger.success("操作完成")

# 临时上下文(with 块内有效)
with logger.contextualize(request_id="req-abc123"):
    logger.info("处理请求开始")
    logger.info("处理请求结束")  # 两条日志都会带上 request_id

6. 高级特性

异步日志记录

在异步应用中,使用 enqueue=True 开启非阻塞写入:

from loguru import logger

logger.add("async.log", enqueue=True)  # 异步写入,不阻塞主线程

async def handle_request():
    logger.info("请求处理中...")  # 日志写入不影响响应速度

自定义日志级别

from loguru import logger

# 添加自定义级别(no 为级别数值,介于 DEBUG=10 和 INFO=20 之间)
logger.level("TRACE_CUSTOM", no=15, color="", icon="🔍")
logger.log("TRACE_CUSTOM", "这是自定义级别的日志")

条件日志(环境区分)

from loguru import logger
import os, sys

logger.remove()
is_dev = os.getenv("ENV") == "development"
logger.add(sys.stderr, level="DEBUG" if is_dev else "INFO")

美化输出

from loguru import logger

logger.info("🚀 服务启动成功!")
logger.info("成功 执行了操作")
logger.error("失败:发生错误")

7. 将标准库 logging 重定向到 Loguru

这是本文的核心主题。当项目中已有大量使用标准库 logging 的第三方库(如 FastAPI、Uvicorn、SQLAlchemy)时,通过 InterceptHandler 可以将它们的日志也统一交由 Loguru 处理。

核心原理

标准库 logging 支持自定义 Handler。在 emit() 方法中:

  1. logging 的级别名称映射到 Loguru 对应级别

  2. 向上追踪真实调用栈,确保日志来源文件/行号准确

  3. 将异常信息一并传递给 Loguru

完整实现

from loguru import logger
import logging


class InterceptHandler(logging.Handler):
    """将标准库 logging 重定向到 Loguru 的拦截处理器"""

    def emit(self, record: logging.LogRecord) -> None:
        # 将 logging 级别映射到 Loguru 级别
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # 追踪真实调用栈,确保文件名和行号准确
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        # 转发日志到 Loguru(携带异常信息)
        logger.opt(depth=depth, exception=record.exc_info).log(
            level, record.getMessage()
        )


def setup_logging(level: str = "INFO") -> None:
    """全局配置:将所有 logging 输出重定向到 Loguru"""
    logging.root.handlers = []
    logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)

    # 拦截常见第三方库的 logger
    for name in ["uvicorn", "uvicorn.error", "uvicorn.access", "fastapi", "sqlalchemy"]:
        logging.getLogger(name).handlers = [InterceptHandler()]
        logging.getLogger(name).propagate = False

在项目中使用

from loguru import logger
import logging
from your_module import setup_logging

# 在应用启动时调用一次即可
setup_logging(level="DEBUG")

# 之后无论是标准库还是 Loguru,输出格式完全统一
logging.getLogger("myapp").info("标准库日志,已被 Loguru 接管")
logging.warning("警告信息,同样走 Loguru")
logger.info("Loguru 原生日志")

8. 与 FastAPI / Uvicorn 集成

FastAPI 和 Uvicorn 内部大量使用标准库 logging,通过以下方式可以将所有日志统一收归 Loguru 管理:

from fastapi import FastAPI
from loguru import logger
import logging
from contextlib import asynccontextmanager


class InterceptHandler(logging.Handler):
    def emit(self, record):
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1
        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时统一重定向日志
    logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
    for name in ["uvicorn", "uvicorn.error", "uvicorn.access"]:
        logging.getLogger(name).handlers = [InterceptHandler()]
        logging.getLogger(name).propagate = False
    logger.info("🚀 日志系统初始化完成")
    yield


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def root():
    logger.info("处理请求: GET /")
    return {"message": "Hello World"}

9. 注意事项

  1. 调用时机setup_logging() 必须在所有第三方库 import 之后、应用启动之前调用,否则部分库的 logger 已完成初始化,拦截可能不完整。

  2. propagate 设置:对单独拦截的第三方库 logger,记得将 propagate 设为 False,避免日志被重复输出。

  3. level=0logging.basicConfig(level=0) 接受所有级别,具体过滤由 Loguru 负责。

  4. 多进程环境:在 Gunicorn 等多进程场景下,每个子进程需独立初始化,建议在进程启动钩子中调用 setup_logging()

  5. 异步场景:在高并发异步应用中,使用 logger.add(..., enqueue=True) 开启异步写入,避免 I/O 阻塞影响性能。

总结

Loguru 是 Python 日志处理的终极方案。通过 InterceptHandler 拦截标准库 logging,可以将整个项目(包括所有第三方库)的日志统一交由 Loguru 处理,做到:

  1. ✅ 与标准库 logging 完全兼容,零侵入改造

  2. ✅ 享受 Loguru 的彩色输出、异常追踪、文件轮转等现代特性

  3. ✅ 统一日志格式,告别多套日志系统并存的混乱局面

如果你的项目还在用裸的 logging,现在就可以迁移了——代价极低,收益显著。

博主关闭了所有页面的评论