
loguru 中多个 `add()` 添加的处理器默认共享同一 logger 实例,导致日志按最低匹配级别广播到所有符合条件的 sink;要实现“error 仅写 error.log、info 仅写 info.log”,必须使用 `filter` 参数进行精确路由,而非仅依赖 `level`。
在 Loguru 中,logger 是一个全局单例对象(from loguru import logger 导入的是同一个实例),因此 loggerEven = logger 和 loggerUneven = logger 实际指向完全相同的 logger。调用两次 logger.add() 只是为该单一 logger 追加了两个输出目标(sinks),而非创建两个独立的日志器。
关键误区在于:level 参数不控制日志“是否发送到该 sink”,而是定义该 sink 的接收门槛——即:只要日志级别 ≥ 指定 level,该 sink 就会接收该日志。例如:
- logger.add("Even.txt", level="INFO") → 接收 INFO 及更高级别(WARNING, ERROR, CRITICAL)的日志;
- logger.add("UnEven.txt", level="ERROR") → 接收 ERROR 及更高级别(CRITICAL)的日志。
因此,当执行 logger.error("Uneven") 时,其级别为 ERROR,同时满足 INFO(≥ INFO)和 ERROR(≥ ERROR)两个条件,于是被写入两个文件——这正是问题中观察到的“重复写入”现象。
✅ 正确解法:使用 filter 参数实现精确路由。filter 可接收函数、字符串或字典,用于决定某条日志是否应被当前 sink 处理。推荐方式是传入一个返回布尔值的函数:
from loguru import logger
import random
# 清空默认控制台 handler(可选,避免干扰)
logger.remove()
# ✅ 仅接收 INFO 级别且 message 不含 "ERROR" 的日志(或更通用:仅 INFO)
logger.add("Even.txt", level="INFO", filter=lambda record: record["level"].no == 20) # INFO 的 no 是 20
# ✅ 仅接收 ERROR 级别日志(no == 40)
logger.add("UnEven.txt", level="ERROR", filter=lambda record: record["level"].no == 40)
x = random.randint(0, 11)
print(x)
if x % 2 == 1:
logger.error("Uneven") # → 仅写入 UnEven.txt
else:
logger.info("Even") # → 仅写入 Even.txt? 补充说明:
- record["level"].no 是 Loguru 内部定义的日志级别数值(DEBUG=10, INFO=20, WARNING=30, ERROR=40, CRITICAL=50),比字符串比较更可靠;
- 若需更灵活控制(如按模块名、自定义字段过滤),filter 函数还可检查 record["module"]、record["extra"] 等字段;
- 切勿通过赋值别名(如 loggerEven = logger)试图“隔离” logger——Loguru 不支持此模式;如需真正独立日志器,请使用 logger.bind() + filter 或按官方 Recipe 创建子 logger(见文档 Creating independent loggers)。
总结:Loguru 的 level 是“最低准入门槛”,filter 才是“精确通行闸机”。要实现日志文件级隔离,务必以 filter 为核心逻辑,辅以合理的 level 设置,方能确保每条日志只落盘到预期的目标文件。










