YydsLogger
基于 Loguru 的增强日志记录器,提供稳定的本地日志写入、多语言、异步队列、结构化输出和生命周期管理能力。
特性
- 多语言输出(zh/en)
- 所有运行时中英文提示集中维护在
yyds_logger/i18n.py - 自定义格式、级别过滤、按文件大小轮转、保留策略、gzip 压缩
- 多实例物理隔离:底层内聚了完全独立的
yyds_loguru引擎,彻底打破全局单例限制。每个YydsLogger实例拥有独立的 Core,且contextualize()上下文绑定同样物理隔离,互不串扰。 - JSON 结构化输出(serialize):直接对接 ELK / Loki / Datadog / CloudWatch
- 接管标准库 logging:把 uvicorn / sqlalchemy 等三方库日志统一汇入本管道(通过
sys._getframeC 级原生帧跳过深度优化性能) - 环境感知(env='prod'/'dev'):默认为
'prod'(生产环境优先),生产自动关闭 diagnose/backtrace,使用异步文件写入 - 有界本地队列:支持
queue_size、block/drop溢出策略和queue_timeout - writer 侧延迟格式化:线程队列可用
defer_format=True将格式化、异常渲染和 JSON 序列化移到 writer,降低业务线程日志延迟 - 按 sink 独立级别:控制台、主文件可各自设置级别
- request_id 上下文注入(ContextVar)+
bind/contextualize结构化字段与 trace 关联 - 装饰器记录函数调用与耗时(同步/异步),级别关闭时自动跳过昂贵格式化
- 轻量基础统计(总量、级别计数、错误率)
- 可选健康检查(磁盘、内存、日志文件数量和大小)
- 纯零核心依赖:移除了对外部
loguru包的硬性安装依赖,基础版零额外依赖,轻量可靠且彻底杜绝依赖版本冲突风险 - 优雅资源管理:支持
flush()、flush_async()、close()、aclose()、同步/异步上下文管理和可选 SIGTERM 处理
安装
需要 Python 3.9 或更高版本。
pip install -U yyds-logger
核心安装为纯零额外依赖(移除了对外部 loguru 的强制依赖,无版本冲突风险)。按需安装可选能力:
pip install "yyds-logger[profile]" # 行级耗时分析(line_profiler,更精确)
pip install "yyds-logger[monitoring]" # 系统指标与健康检查(psutil)
pip install "yyds-logger[all]" # 一次性安装全部可选依赖
未装
line_profiler时,time_it(line_by_line=True)自动回退到原生sys.settrace。
快速开始
基本使用
from yyds_logger import YydsLogger
# 创建日志记录器实例
logger = YydsLogger(
file_name="app",
log_dir="logs",
language="zh" # 使用中文输出
)
# 设置 request_id(每条日志会带 ReqID:xxx)
token = logger.request_id_var.set("req-001")
try:
logger.info("这是一条信息日志")
logger.warning("这是一条警告日志")
logger.error("这是一条错误日志")
finally:
logger.request_id_var.reset(token)
# 程序退出前清理(排空本地队列、恢复 hook、移除 handler)
logger.cleanup()
需要在程序继续运行时仅排空异步队列,可调用 logger.flush();完全关闭并释放资源可调用
logger.close(),它是 cleanup() 的明确别名。
在已有 asyncio 事件循环的协程中使用:
await logger.flush_async()
await logger.aclose()
# 也可以自动异步关闭
async with YydsLogger("app") as logger:
logger.info("处理中")
request_id 用法与并发说明
request_id_var 是一个 ContextVar,用来给“同一条业务链路”的日志自动带上 ReqID:...:
set(value):把当前上下文的 request_id 设为value,并返回一个 token(表示 set 之前的旧值)reset(token):把当前上下文恢复到 set 之前的值,避免 request_id 泄漏到下一次请求/任务
推荐写法用 try/finally 确保一定 reset(如上例)。
多并发下的行为:
- 多线程:不同线程之间的
request_id互不影响;但新线程不会自动继承父线程的 request_id,需要显式传递。 - asyncio:不同 Task 之间的
request_id互不影响;创建 Task 时会复制一份当前上下文,所以要在create_task()之前 set。
线程/线程池中传递 request_id 的示例:
import contextvars
import threading
token = logger.request_id_var.set("req-001")
try:
ctx = contextvars.copy_context()
t = threading.Thread(target=lambda: ctx.run(logger.info, "子线程日志也带 ReqID"))
t.start()
t.join()
finally:
logger.request_id_var.reset(token)
asyncio 并发示例(每个请求独立 request_id):
import asyncio
async def handle(req_id: str):
token = logger.request_id_var.set(req_id)
try:
logger.info("开始处理")
await asyncio.sleep(0.1)
logger.info("处理完成")
finally:
logger.request_id_var.reset(token)
async def main():
await asyncio.gather(handle("req-1"), handle("req-2"))
asyncio.run(main())
生产环境推荐配置(env)
默认环境为 'prod'。生产模式会关闭 diagnose/backtrace(避免在日志里泄漏变量值并降低开销),
同时保持 enqueue=True(异步文件写入):
logger = YydsLogger(
file_name="app",
log_dir="logs",
# env="prod", # 默认即为 "prod",生产关闭诊断回溯,使用异步文件写入
serialize=True, # 文件输出 JSON,便于日志平台采集
)
多进程配置
多进程部署必须使用 process_isolation=True,让不同进程写入独立的 PID 日志文件,避免轮转和写入竞争;默认值为 False。对于 Gunicorn、uWSGI 等 pre-fork 模式,即使 logger 在 master 中创建,worker 也会在 fork 后自动重建自己的 sink 并使用 worker PID 文件名。使用 spawn 模式时请在子进程内创建 logger,不要将实例作为进程参数传递。
本地文件 sink 的 enqueue 队列可通过 queue_size、overflow_policy、queue_timeout、
queue_backend 和 shutdown_timeout 配置。队列满时可选择阻塞或丢弃;默认 block 最多等待
1 秒,flush/close 的每次队列控制或 writer join 默认最多等待 30 秒。将
shutdown_timeout 设为 None 可恢复无限等待。
当格式化本身(动态 format、异常回溯或 JSON 序列化)较重时,可对本地文件 sink 打开
defer_format=True。它要求 enqueue=True 且 queue_backend="thread",日志调用只复制当时的
record 快照并入队,实际格式化由 writer 执行;因此动态 format 函数的执行线程和执行时机也会改变。
queue_backend="auto" 默认使用进程内线程队列,避免 pickle、IPC、额外 feeder 线程和文件描述符开销。
多进程部署配合 process_isolation=True 时,每个 worker 同样使用自己的线程队列和 PID 文件。
只有确实依赖 multiprocessing 队列语义时才应显式选择 "multiprocessing";不可 pickle 的结构化字段
会被拒绝并计入 dropped_messages 与 serialization_errors,可通过 get_queue_status() 查看。
运行中可读取队列深度、容量、利用率、writer 状态和各类丢弃原因:
status = logger.get_queue_status()
print(status["depth"], status["utilization"], status["drop_reasons"])
需要切换格式、级别或队列策略时,可用 reconfigure() 原子替换本实例管理的 sinks;失败时旧配置会继续生效:
logger.reconfigure(file_level="DEBUG", defer_format=True)
如需精细控制,可用 enqueue / diagnose / backtrace 三个可选参数显式覆盖:
logger = YydsLogger("app", env="prod", diagnose=True) # 临时排障:单独打开 diagnose
JSON 结构化日志(serialize)
容器 / k8s / 日志平台(ELK、Loki、Datadog)更适合消费 JSON。serialize 控制文件输出,
console_serialize 控制控制台输出:
logger = YydsLogger(
file_name="app",
serialize=True, # 文件写 JSON(每行一条)
console_serialize=False, # 控制台仍保留彩色可读格式
)
logger.info("用户登录")
# 文件中为一行 JSON,包含 time/level/message/extra(含 request_id 及 bind/contextualize 字段)
注意:序列化模式下日志为 JSON,自定义彩色
format的颜色不再生效(这是 loguru 序列化语义)。
结构化上下文与 trace 关联(bind / contextualize)
配合 serialize=True,绑定的字段会自动出现在 JSON 中,方便与 OpenTelemetry / 链路追踪对齐:
# 永久绑定,返回带上下文的 logger
logger.bind(service="order", trace_id="abc123").info("处理下单")
# with 作用域内临时注入(线程/协程安全)
with logger.contextualize(trace_id="abc123", span_id="01"):
logger.info("查询库存")
logger.info("扣减库存")
# request_id 便捷方法
token = logger.set_request_id("req-001")
logger.info(logger.get_request_id())
接管标准库 logging
把三方库(uvicorn、sqlalchemy、requests 等)通过标准库 logging 输出的日志统一汇入本管道:
logger = YydsLogger("app")
# 按需接管指定 logger;不传 names 时接管 root logger。
logger.capture_std_logging(level="INFO", names=["uvicorn", "sqlalchemy.engine"])
默认不会删除目标 logger 已有的 handlers;如需完全接管,可显式传入
clear_existing=True。cleanup() 时会自动恢复被修改的标准库 logging 状态。
未处理异常
如需把主线程和子线程的未处理异常写入日志,可在实例创建后显式安装 hook:
logger = YydsLogger("app")
logger.setup_exception_handler()
异常 hook、标准库 logging 接管和信号处理器均属于进程全局资源;同一时间只能由一个
YydsLogger 实例接管,cleanup() 会恢复本实例仍拥有的原处理器。
按 sink 设置独立级别
控制台、主文件可分别设级别(例如控制台只看 WARNING,文件留全量 DEBUG):
默认不设置 console_level 和 file_level 时,主文件与控制台不进行级别过滤。
logger = YydsLogger(
file_name="app",
console_level="WARNING",
file_level="DEBUG",
)
优雅退出(SIGTERM)
容器 / k8s 用 SIGTERM 停服时 atexit 不一定触发,开启后信号处理器只发送通知,后台清理线程
负责在有界超时内排空 enqueue 队列,避免在 Python 信号上下文中执行文件 I/O:
logger = YydsLogger("app")
logger.setup_signal_handlers() # 拦截并链回原 SIGTERM/SIGINT 处理器
异步函数支持
import asyncio
@logger.log_decorator()
async def async_function():
await asyncio.sleep(1)
return "异步操作完成"
# 使用异步函数
async def main():
result = await async_function()
logger.info(f"异步函数结果: {result}")
asyncio.run(main())
增强错误信息
# 错误日志现在会显示详细的位置信息
@logger.log_decorator("除零错误", level="ERROR")
def divide_numbers(a, b):
return a / b
try:
result = divide_numbers(1, 0)
except ZeroDivisionError:
logger.exception("捕获到除零错误")
# 输出示例:
# 2025-01-03 10:30:15.123 | ERROR | ReqID:REQ-123 | app.py:25:divide_numbers | 12345 | 除零错误 [ZeroDivisionError]: division by zero | 位置: app.py:25:divide_numbers | 代码: return a / b
# 调用链: app.py:25:divide_numbers -> main.py:10:main
log_decorator() 的 trace 控制详细异常栈;reraise 控制异常是否继续抛出,默认值为
True,不会因关闭详细堆栈而吞掉业务异常:
@logger.log_decorator(trace=False, reraise=True)
def still_raise():
raise ValueError("业务异常仍交给调用方处理")
耗时统计与行分析 (@time_it)
提供 @logger.time_it 装饰器,不仅可以记录函数的整体执行耗时,还能开启行级性能分析,扒开函数内部看哪一行代码在拖后腿(支持同步和异步函数)。
import time
import asyncio
# 1. 最简使用:只查看耗时,不带括号直接装饰(每次都打印耗时)
@logger.time_it
def my_task():
time.sleep(0.05)
my_task()
# 输出示例:
# 2026-05-25 15:37:14.790 | INFO | ReqID:no-request-id | yyds_logger.py:778 | [TIMER] 函数 `my_task` 执行完毕 | 总耗时: 52.15ms
# 2. 进阶版:开启行级性能分析(支持可选依赖 line_profiler,未安装时自动降级为原生 sys.settrace)
@logger.time_it(line_by_line=True)
def heavy_task():
a = 1 + 2
time.sleep(0.02) # 耗时较短
b = a * 3
time.sleep(0.08) # 耗时最长,被标记为瓶颈
return b
heavy_task()
# 输出示例:
# ================================================================================
# [FN-TIMER] 行性能分析报告 -> 函数: `heavy_task`
# ================================================================================
# 行号 | 命中次数 | 总耗时 (ms) | 每步耗时 (ms) | 耗时占比 | 源代码
# --------------------------------------------------------------------------------
# 45 | 1 | 0.00 | 0.00 | 0.0% | a = 1 + 2
# 46 | 1 | 25.05 | 25.05 | 22.8% | time.sleep(0.02) # 这行稍慢 <-- 🚨 性能瓶颈
# 47 | 1 | 0.00 | 0.00 | 0.0% | b = a * 3
# 48 | 1 | 84.66 | 84.66 | 77.1% | time.sleep(0.08) # 这行最慢,应当被标记为瓶颈 <-- 🚨 性能瓶颈
# 49 | 1 | 0.00 | 0.00 | 0.0% | return b
# --------------------------------------------------------------------------------
# 总耗时: 109.78 毫秒
# ================================================================================
健康检查(可选)
健康检查依赖 psutil,可通过可选依赖安装:
pip install yyds-logger[monitoring]
from yyds_logger import LogHealthChecker
checker = LogHealthChecker()
health = checker.check_health("logs")
print(health["status"])
print(health["metrics"]["disk_usage_percent"])
# 如需同时查看 logger 生命周期、handler 数量和队列丢弃数:
print(logger.get_health())
日志统计功能
# 启用统计功能
logger = YydsLogger(
file_name="app",
enable_stats=True
)
# 获取统计信息
stats = logger.get_stats()
print(stats)
高级配置
完整配置示例
logger = YydsLogger(
file_name="app", # 日志文件名
log_dir="logs", # 日志目录
max_size=10, # 单个日志文件最大大小(MB)
retention="7 days", # 日志保留时间
language="zh", # 日志语言(zh/en)
custom_format=None, # 自定义日志格式
compression="gz", # 日志压缩格式,默认 gzip
enable_stats=False, # 是否启用统计
# —— 新增参数 ——
env="prod", # "dev"/"prod"
enqueue=None, # 显式覆盖 enqueue(异步文件写入)
diagnose=None, # 显式覆盖 diagnose
backtrace=None, # 显式覆盖 backtrace
serialize=False, # 文件输出 JSON 结构化日志
console_serialize=False, # 控制台输出 JSON 结构化日志
console_level=None, # 控制台独立级别(默认不过滤)
file_level=None, # 主文件独立级别(默认不过滤)
queue_size=10000, # 本地 enqueue 队列容量,None 表示无界
overflow_policy="block", # 队列满时 block 或 drop
queue_timeout=1.0, # block 模式最大等待时间;None 表示无限等待
queue_backend="auto", # 默认解析为 thread
shutdown_timeout=30.0, # 队列控制或 writer join 的单次等待上限
defer_format=False, # 在线程队列 writer 侧执行格式化/序列化
process_isolation=False, # 多进程部署时必须改为 True
)
保留与压缩
retention 只清理当前 sink 的轮转归档,不会删除正在写入的活动文件,也不会扫描另一类
sink 的文件:
# 默认:轮转归档为 .log.gz,保留策略作用于这些压缩归档
YydsLogger("app", retention="7 days", compression="gz")
# 不压缩:轮转归档保持为普通 .log,保留策略作用于这些普通归档
YydsLogger("app", retention="7 days", compression=None)
保留清理由下一次日志轮转触发;如果一直没有新的轮转,过期归档会保留到下一次轮转时再清理。
自定义日志格式
custom_format = (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
"<level>{level: <8}</level> | "
"ReqID:{extra[request_id]} | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
)
logger = YydsLogger(
file_name="app",
custom_format=custom_format
)
主要功能
1. 日志记录
- 支持所有标准日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL)
- 支持
log()通用级别入口 - 支持 request_id、bind/contextualize 结构化上下文
2. 日志管理
- 自动日志轮转
- gzip 日志压缩
- 日志保留策略
- 本地 enqueue 队列与溢出策略
3. 统计功能
- 日志总数统计
- 错误率统计
- DEBUG/INFO/WARNING/ERROR 级别计数
- 统计开关关闭时不产生统计开销
4. 装饰器支持
- 函数执行时间记录
- 异常捕获和记录
- 支持同步和异步函数
5. 增强错误信息
- 显示错误发生的具体文件、行号和函数名
- 显示错误发生时的代码行内容
- 显示调用链信息(最后3层调用)
- 支持全局异常处理器
6. 性能优化
- 级别门控减少无效日志构造
- 可选 enqueue 异步文件写入
- 有界队列支持阻塞或丢弃策略
- 行级性能分析作为独立可选能力
7. 运维能力
- 可选接管标准库
logging - 可选异常钩子和 SIGTERM/SIGINT 生命周期处理
- 可选健康检查:磁盘、内存和日志文件状态
性能基准
仓库提供零额外依赖的基准工具,用于比较同步写入、异步阻塞队列和有界丢弃队列的生产端吞吐、 P50/P95/P99 延迟、排空时间与丢弃量:
python benchmarks/bench_logging.py --mode all --messages 20000 --threads 4
python benchmarks/bench_logging.py --mode enqueue --serialize --json
基准会使用临时日志目录,不会污染项目目录;结果受磁盘、CPU、Python 版本与并发负载影响,应在目标部署环境中比较。
错误处理
try:
logger = YydsLogger("app", log_dir="/path/to/logs")
except RuntimeError as e:
print(f"日志配置失败: {e}")
注意事项
- 确保日志目录具有写入权限
- 生产环境建议使用
env="prod"+serialize=True,并按需调用setup_signal_handlers() - 异步操作时注意正确处理异常
serialize=True时日志为 JSON,自定义彩色format的颜色不再生效YydsLogger实例的日志 sink 相互隔离;但标准库 logging、异常 hook 和信号处理器属于进程全局资源,同一时间只能由一个实例接管
贡献
欢迎提交 Issue 和 Pull Request!
许可证
MIT License
本发行版内置了经修改的 Loguru 引擎;完整归属与许可证说明见 THIRD_PARTY_NOTICES.md。
Metadata
Release files for yyds-logger 0.8.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| yyds_logger-0.8.1.tar.gz | 112.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| yyds_logger-0.8.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 214.8 kB
Release files / yyds_logger-0.8.1.tar.gz
| Download URL | yyds_logger-0.8.1.tar.gz |
|---|---|
| Size | 112.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
48c09b3c6fef183994f2afe182913162e9f56e8a138799dc22fc079ecd66917c
|
|
BLAKE2b-256 checksum How to use checksums |
2e96a4fd1f66472ec9ced3d63e386385c2e7edc0e350697de2470103c66c5953
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.11
|
Release files / yyds_logger-0.8.1-py3-none-any.whl
| Download URL | yyds_logger-0.8.1-py3-none-any.whl |
|---|---|
| Size | 102.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d34331d0fcfbf73c3495334a50ff82ca9eb6a5df5c7b3a2af698771d4a6fab7b
|
|
BLAKE2b-256 checksum How to use checksums |
e4c546f778af2152fbf99f3a9fa1ed8234bffdab50cb9dca8694af81cf31fad3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.11
|