KernelRanch
返回文章列表

loguru 0.7 日志:一个 logger 走到底,和三个默认行为

2026-11-10 #Python

标准库 logging 要配 logger、handler、formatter、filter 四层对象,写对一次之后很稳,但每开一个新项目都要再抄一遍配置;轮转、压缩、JSON 序列化还要额外装 handler。loguru 把这些收进一个全局 logger 和 add():logger.add("app.log", rotation="10 MB", retention="7 days") 一行就把文件、轮转、清理配完。代价是它和标准库的 logging 是两套东西,项目里已经有一堆 logging.getLogger() 的时候,接进来要写适配层。

安装与实际版本

python -m venv D:\ai\cache\temp\py-loguru
D:\ai\cache\temp\py-loguru\Scripts\python.exe -m pip install loguru
Name: loguru
Version: 0.7.3
Location: D:\ai\cache\temp\py-loguru\Lib\site-packages

最小可运行示例

默认就有一个输出到 stderr 的 handler,级别是 DEBUG:

import sys

from loguru import logger

logger.debug("这条 DEBUG 会出来")
logger.info("带变量的写法 {}", "和 str.format 一样")
logger.warning("警告")

换成文件 sink,顺带把轮转和清理配好:

import os
import tempfile

from loguru import logger

workdir = os.path.join(tempfile.gettempdir(), "logdemo")
os.makedirs(workdir, exist_ok=True)
logfile = os.path.join(workdir, "app.log")

logger.remove()  # 去掉默认的 stderr sink
logger.add(
    logfile,
    level="INFO",
    rotation="2 KB",
    retention=3,
    encoding="utf-8",
    format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{line} | {message}",
)

for i in range(60):
    logger.info(f"第 {i:02d} 条日志,填充内容 {'x' * 60}")
logger.complete()

print("目录里的文件:", sorted(os.listdir(workdir)))
print("app.log 的大小:", os.path.getsize(logfile), "字节")
目录里的文件: ['app.2026-11-10_14-08-55_778311.log', 'app.2026-11-10_14-08-55_781670.log', 'app.2026-11-10_14-08-55_782692.log', 'app.log']
app.log 的大小: 1160 字节

60 条日志撑出 3 个归档文件,当前 app.log 停在 1160 字节,没超过 2 KB 的阈值。归档文件名是 app.<时间戳>.log,retention=3 表示同类文件最多留 3 个。

结构化日志用 serialize=True,每行一个 JSON:

jsonfile = os.path.join(workdir, "app.jsonl")
logger.add(jsonfile, serialize=True, level="INFO")
logger.bind(user="u-17", action="checkout").info("下单成功")
logger.complete()

with open(jsonfile, encoding="utf-8") as fh:
    lines = fh.read().strip().splitlines()
print("行数:", len(lines))
print("最后一行:", lines[-1])
行数: 1
最后一行: {"text": "2026-11-10 14:08:55.785 | INFO     | __main__:<module>:52 - 下单成功\n", "record": {"elapsed": {"repr": "0:00:00.040262", "seconds": 0.040262}, "exception": null, "extra": {"user": "u-17", "action": "checkout"}, "file": {"name": "loguru_demo.py", "path": "D:\\ai\\cache\\temp\\work\\loguru_demo.py"}, "function": "<module>", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 52, "message": "下单成功", "module": "loguru_demo", "name": "__main__", "process": {"id": 22592, "name": "MainProcess"}, "thread": {"id": 42184, "name": "MainThread"}, "time": {"repr": "2026-11-10 14:08:55.785878+08:00", "timestamp": 1794290935.785878}}}

bind() 传进去的 user 和 action 落在 record.extra 里,采集端按字段取就能用,不用解析 text。time 带时区偏移,process/thread 也都在里面。

常用 API 与参数

名称作用要点
logger.add(sink, ...)注册输出sink 可以是路径、文件对象、标准流、函数、协程
rotation=轮转支持 "10 MB"、"1 week"、"12:00"
retention=清理旧文件按数量或时长,如 3、"10 days"
compression=归档压缩"zip"、"gz",在关闭文件时执行
format=格式串花括号字段:{time}、{level}、{name}、{line}、{extra[x]}
serialize=True输出 JSON 行整条 record 一起序列化
logger.bind()绑定上下文关键字参数进 extra,可链式再 bind
logger.contextualize()临时上下文关键字参数在 with 块内生效,适合请求级字段
logger.catch捕获异常装饰器或上下文管理器,异常记录后不再抛出
logger.remove(handler_id)移除 sinkremove() 不带参数清空全部,含默认的 stderr

三个默认行为

第一个是默认 sink 指向 stderr,不是 stdout。文档写的是"pre-configured and outputs to stderr"。这在交互式终端里没差别,但用 subprocess 抓 stdout、或者 python app.py > out.txt 时,日志不会进那个文件。本机跑的时候能直接看到后果:重定向到同一个流之后,stderr 的内容最后才一起刷出来,顺序和代码里的调用顺序不一致。

14:08:55 | INFO     | 这条走 stderr
14:08:55 | WARNING  | WARNING 会出现
14:08:55 | INFO     | 开始处理订单
14:08:55 | INFO     | req-8f21 | 开始处理订单

对应的 stdout 只有 (上面这行是 stderr 输出,stdout 只有本行) 这一句。要么把 sink 显式指到 sys.stdout,要么采集端两个流都收。

第二个是与标准库 logging 互不相通。两个都配好之后,各写各的:

std INFO 标准库日志
标准库的 handler 与 loguru 互不相通

第三方库大多用标准库 logging,它们的日志不会出现在 loguru 的 sink 里。要互通得自己写桥:把 logging 的记录转给 loguru 用 InterceptHandler,反方向用 PropagateHandler。文档给了这两个类的完整写法,都是一二十行。

第三个是 @logger.catch 默认带诊断信息。它不只是打印回溯,还会把栈帧里的变量值画出来:

14:08:55 | ERROR    | An error has been caught in function '<module>', process 'MainProcess' (22592), thread 'MainThread' (42184):
Traceback (most recent call last):

> File "D:\ai\cache\temp\work\loguru_demo.py", line 69, in <module>
    print("返回值:", divide(1, 0))
                  └ <function divide at 0x0000018644B776A0>

  File "D:\ai\cache\temp\work\loguru_demo.py", line 66, in divide
    return a / b
           │   └ 0
           └ 1

ZeroDivisionError: division by zero

└ 0、└ 1 就是实参的值。开发期很好用,生产环境就可能把密码、token 打进日志。文档自己也标了提醒:diagnose=True 是默认值,可能泄露敏感数据。上生产前把 diagnose=False、backtrace=False 配上。

另外,被 @logger.catch 捕获后函数返回 None,异常不再向上抛:

返回值: None
程序继续往下走

主流程里这么用会把错误吞掉,需要让异常继续传播时加 reraise=True。

小结

日志库的分歧点不在写法,在"谁来收集"。loguru 把格式、轮转、序列化收进 add(),代价是与标准库生态隔开:用它的项目里,第三方库的日志要靠适配层才看得见。

三个默认值决定上线后的行为:sink 在 stderr、诊断信息默认开、多进程默认不安全(文档要求 enqueue=True 才保证多进程写入完整)。前两个改配置就行,第三个要改 add() 的参数,而且不加的话症状是日志偶发缺行或者串行错乱,很难归因。

数据来源与检索时间

检索时间 2026-11-10。版本、文件列表、JSON 记录与回溯文本为本机实测。

来源URL对应数据
PyPI《loguru》项目页https://pypi.org/project/loguru/包名、安装方式与当前版本 0.7.3
loguru 官方文档《Overview》https://loguru.readthedocs.io/en/stable/overview.html"预配置并输出到 stderr"的默认行为;add() 统一管理 sink/格式/级别/过滤;rotation/retention/compression 的取值;serialize=True 与 bind() 的结构化日志;@logger.catch 捕获异常;diagnose=True 为默认且可能泄露敏感数据的提醒;多进程需 enqueue=True;InterceptHandler/PropagateHandler 与标准库互通的写法
本机运行记录D:\ai\cache\temp\work\loguru_demo.py0.7.3;默认 stderr 与级别过滤;bind 的 extra 输出;2 KB 轮转产生的 4 个文件与 1160 字节;app.jsonl 的完整 record;@logger.catch 的回溯与返回值;标准库日志并排输出的结果

核验说明

  • 虚拟环境 D:\ai\cache\temp\py-loguru,Python 3.11.9,pip install loguru 装到 0.7.3。
  • 真跑的示例:默认 sink、级别过滤、bind 与 format 里的 {extra[...]}、文件 sink 配 rotation="2 KB" + retention=3、serialize=True 的 JSON 行、@logger.catch 捕获除零、{name}:{function}:{line} 格式、与标准库 logging 并排输出。
  • 没测的部分:compression(zip/gz)、contextualize、logger.patch、自定义级别、enqueue=True 的多进程写入、InterceptHandler 的实际对接、logger.parse()。这些文档里有完整示例,但没有在本机验证,文章里没有给性能或行为结论。
  • 多进程那条是从文档结论转述的(文档明确写 "thread-safe by default... not multiprocess-safe, but you can enqueue"),本机只跑了单进程,没有复现丢日志的现象。