## 1. 为什么说loguru是Python日志记录的“优雅”新选择?
如果你写过Python,十有八九用过或者至少见过`logging`这个标准库。说实话,我第一次用`logging`的时候,感觉就像在组装一台复杂的机器。你得先创建一个`logger`,然后配置`handler`,再设置`formatter`,最后还得把它们像拼乐高一样组合起来。想输出到控制台和文件?没问题,再来一套。想给不同模块设置不同格式?配置文件的复杂度直接翻倍。这还没完,等你兴冲冲地把这套东西放到多线程或者异步任务里跑,日志内容可能就乱成一锅粥,甚至直接丢失,查问题的时候简直让人抓狂。
后来我遇到了`loguru`,第一感觉是:这也太简单了吧?简单到有点不真实。它把上面那一整套繁琐的流程,浓缩成了几乎“开箱即用”的一行导入。你不用再纠结`logger`、`handler`、`formatter`这些概念,一个`logger`对象已经为你准备好了。这种设计哲学上的差异,是`loguru`“优雅”的第一个体现:**极简的API设计**。它把复杂性封装在内部,暴露给开发者的接口直观且一致。
但“优雅”绝不仅仅是简单。`loguru`在保持简洁的同时,功能一点也没打折。它默认的输出就非常友好:时间戳、日志级别、模块名、行号、日志内容,一目了然。更棒的是,它在控制台输出时**自带彩色高亮**,错误信息用醒目的红色,警告信息用黄色,调试信息用柔和的颜色,一眼就能定位关键信息。这种对开发者体验的细致考量,是“优雅”的第二个层面。
更深层次的“优雅”,在于它对现代开发场景的适配。比如,它原生支持**结构化日志**,可以轻松地将日志序列化为JSON格式,方便接入ELK(Elasticsearch, Logstash, Kibana)等日志分析平台。再比如,它内置了强大的**日志文件管理功能**,通过几个直观的参数就能实现按时间、按大小分割日志,自动压缩旧文件,甚至定期清理,这些在标准库里都需要你写不少“胶水代码”才能实现。
所以,当你问“为什么选loguru”时,我的回答是:它用极低的认知和操作成本,解决了Python日志记录中绝大多数痛点,并且做得更漂亮、更安全、更适应现代开发流程。它不是要完全取代`logging`,而是为那些厌倦了复杂配置、追求开发效率和代码美感的Python开发者,提供了一个令人愉悦的“新选择”。
## 2. 从安装到上手:5分钟体验loguru的便捷
说再多不如动手试一下。我们花五分钟,从零开始感受一下`loguru`到底有多方便。
### 2.1 一键安装与环境准备
安装`loguru`和安装其他Python库没有任何区别,一条命令搞定:
```bash
pip install loguru
```
我建议你创建一个新的虚拟环境来做实验,避免污染主环境。用`venv`或者`conda`都行。安装成功后,就可以在代码里引用了。
### 2.2 你的第一行loguru日志
让我们来写一个最简单的脚本,就叫它`first_log.py`:
```python
from loguru import logger
logger.debug("这是一条调试信息")
logger.info("程序启动成功")
logger.warning("磁盘空间不足,请注意清理")
logger.error("连接数据库失败!")
logger.critical("系统发生不可恢复的错误,即将退出")
```
保存并运行这个脚本。你会立刻在终端看到彩色的输出,大概长这样:
```
2023-10-27 14:30:15.123 | INFO | __main__:<module>:4 - 程序启动成功
2023-10-27 14:30:15.124 | WARNING | __main__:<module>:5 - 磁盘空间不足,请注意清理
2023-10-27 14:30:15.124 | ERROR | __main__:<module>:6 - 连接数据库失败!
```
看到了吗?我们**没有进行任何配置**。没有创建`logger`对象,没有设置级别,没有指定输出格式。直接导入,直接使用。`loguru`默认的日志级别是`INFO`,所以`DEBUG`信息没有输出。这种“零配置启动”的能力,对于快速原型开发、写小脚本或者教学演示来说,简直是神器。
### 2.3 输出到文件?一行代码的事
光输出到控制台肯定不够,我们通常需要把日志保存到文件里。用`logging`标准库,你得创建`FileHandler`,设置格式,再添加到`logger`上。用`loguru`呢?看好了:
```python
from loguru import logger
# 添加一个文件输出,日志文件名为 app.log
logger.add("app.log")
logger.info("这条信息会同时出现在控制台和 app.log 文件里")
```
对,就一个`logger.add()`。这个方法就是`loguru`的核心魔法,它的`sink`参数可以接收多种目标:字符串(文件名)、文件对象、标准输出/错误流,甚至是一个自定义的函数(比如用来发邮件或写数据库)。这里我们传入文件名`"app.log"`,`loguru`就会自动帮我们创建这个文件,并以默认格式写入日志。
你可能会问,那我想控制输出到文件的格式,或者只让`ERROR`级别以上的日志进文件怎么办?`add()`方法有丰富的参数可以满足你,这个我们后面会详细展开。但就基础使用而言,`logger.add(“文件路径”)`这一行代码的简洁性,已经足够让人心动。
### 2.4 快速修改日志级别
有时候我们只想看错误信息,或者调试时需要看到所有`DEBUG`信息。`loguru`可以动态调整:
```python
import sys
from loguru import logger
# 移除默认的输出到stderr的handler
logger.remove()
# 添加一个新的handler,级别设置为DEBUG
logger.add(sys.stderr, level="DEBUG")
logger.debug("现在能看到我了!")
```
这里先用`logger.remove()`把默认的`handler`去掉(不传参数则移除所有),然后重新`add`一个,通过`level`参数指定级别。同样非常简单直观。
五分钟体验下来,你应该能感受到`loguru`那种“把复杂留给自己,把简单留给用户”的设计理念。它大幅降低了日志记录的门槛,让开发者能更专注于业务逻辑本身,而不是基础设施的搭建。这仅仅是开始,接下来我们看看它更强大的地方。
## 3. 深入核心:loguru的进阶配置与花式玩法
基础功能好用,但真正考验一个库的,是它在复杂场景下的灵活性和能力。`loguru`在这方面同样没让人失望,它通过`logger.add()`方法的一系列参数,提供了极其丰富和强大的配置选项。
### 3.1 定制你的专属日志格式
默认格式不错,但每个项目都有自己的规范。`loguru`的格式化字符串功能强大且直观。比如,我想让日志包含进程ID,并且时间只精确到秒:
```python
import sys
from loguru import logger
# 先移除默认输出
logger.remove()
# 添加一个自定义格式的输出到控制台
logger.add(
sys.stderr,
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <lvl>{level: ^8}</lvl> | PID:{process} | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <lvl>{message}</lvl>",
colorize=True,
)
logger.info("自定义格式的日志")
```
这个`format`字符串里有很多花括号`{}`包围的字段,它们都是`loguru`内置的可用属性:
- `{time}`: 时间,可以用`{time:YYYY-MM-DD HH:mm:ss}`来格式化。
- `{level}`: 日志级别。
- `{message}`: 日志内容本身。
- `{name}`: 产生日志的模块名(通常是`__name__`)。
- `{function}`: 函数名。
- `{line}`: 行号。
- `{process}`、`{thread}`: 进程ID和线程名。
`<green>`, `<lvl>`, `<cyan>`这些是颜色标签,当`colorize=True`时,它们会在终端里渲染成对应的颜色。`<lvl>`标签比较特殊,它会根据日志级别自动切换颜色(INFO绿色,ERROR红色等)。这种格式化方式既灵活又易读,比`logging`里那个有点晦涩的`Formatter`类要友好得多。
### 3.2 强大的日志文件管理:分割、压缩与清理
这是`loguru`让我拍案叫绝的功能之一。在生产环境中,日志文件不能无限增长,我们需要按时间或大小切割,最好还能自动压缩归档,定期删除旧文件。`loguru`用三个参数优雅地解决了所有问题。
**按时间或大小旋转(rotation):**
```python
# 每天午夜创建一个新日志文件
logger.add("runtime_{time}.log", rotation="00:00")
# 每500MB创建一个新日志文件
logger.add("runtime.log", rotation="500 MB")
# 每周创建一个新日志文件
logger.add("runtime.log", rotation="1 week")
# 结合时间和大小:每天或每100MB,先到为准
logger.add("runtime.log", rotation="100 MB | 1 day")
```
`rotation`参数接受时间字符串(如`“00:00”`, `“1 week”`)或大小字符串(如`“500 MB”`, `“1 GB”`)。`{time}`占位符可以用在文件名里,这样分割后的文件会带上时间戳,例如`runtime_2023-10-27_00-00-00.log`,非常清晰。
**自动压缩(compression):**
日志文件多了占空间,`loguru`可以自动压缩旧文件。
```python
# 分割后的旧日志文件自动用zip格式压缩
logger.add("runtime.log", rotation="100 MB", compression="zip")
# 也可以用 tar.gz, gz, bz2 等格式
logger.add("runtime.log", rotation="100 MB", compression="tar.gz")
```
想象一下,如果没有这个功能,你需要写一个定时任务(cron job)或者额外的脚本来做压缩,现在一行参数就搞定了。
**定期清理(retention):**
磁盘空间是宝贵的,不能一直存着历史日志。
```python
# 只保留最近15天的日志文件
logger.add("runtime.log", retention="15 days")
# 只保留最新的10个日志文件
logger.add("runtime.log", retention=10)
# 也可以结合使用:保留最多20个文件,但最多保留30天
logger.add("runtime.log", retention="20 files, 30 days")
```
`retention`参数让你可以按时间或文件数量来清理。这个功能对于在服务器上部署的小型应用特别有用,你再也不用担心日志把磁盘撑爆了。
### 3.3 实现模块化日志与上下文绑定
在大型项目中,我们经常需要区分不同模块的日志,或者给日志加上一些上下文信息(比如用户ID、请求ID)。`logging`标准库通过创建不同的`logger`实例(`logging.getLogger(‘moduleA’)`)来实现。`loguru`的思路略有不同,但更灵活,它采用**绑定(bind)**的方式。
```python
from loguru import logger
# 配置一个通用的格式,其中包含一个额外的字段 {extra[module]}
logger.configure(
handlers=[
{
"sink": sys.stderr,
"format": "<green>{time:HH:mm:ss}</green> | <lvl>{level:8}</lvl> | <m>{extra[module]}</m> - <lvl>{message}</lvl>",
}
]
)
# 为不同的“模块”创建绑定了上下文的logger对象
module_a_logger = logger.bind(module="用户管理模块")
module_b_logger = logger.bind(module="订单处理模块")
module_a_logger.info("用户登录成功")
module_b_logger.warning("订单库存不足")
```
输出会是:
```
14:45:22 | INFO | 用户管理模块 - 用户登录成功
14:45:22 | WARNING | 订单处理模块 - 订单库存不足
```
这里的关键是`logger.bind()`,它会返回一个新的`logger`对象,这个对象“继承”了原有`logger`的所有配置,但额外绑定了一些键值对(存储在`extra`字典中)。然后在格式化字符串里,我们可以用`{extra[key]}`来引用这些值。这种方式非常灵活,你不仅可以绑定模块名,还可以绑定请求ID、会话ID、环境信息等,轻松实现**结构化日志**和**链路追踪**。
### 3.4 捕获异常与回溯:让错误无所遁形
`loguru`对异常处理的支持非常贴心。`logger.exception()`方法或在`logger.catch()`装饰器下,它能自动记录完整的异常堆栈信息,格式清晰易读。
```python
from loguru import logger
@logger.catch
def risky_operation(x, y):
return x / y
# 或者手动记录异常
try:
1 / 0
except ZeroDivisionError:
logger.exception("糟糕,发生了除零错误!")
```
使用`@logger.catch`装饰器后,函数内发生的任何异常都会被自动捕获,并以非常详细的格式(包括变量值)记录到日志中,这对于调试复杂问题帮助巨大。它记录的堆栈信息比简单的`traceback.print_exc()`要丰富和结构化得多。
## 4. 性能与安全:loguru在生产环境中的稳健之道
一个库再好用,如果性能有瓶颈或者在高并发下不稳定,那也无法应用于生产环境。`loguru`在设计之初就考虑了这些严肃的问题。
### 4.1 线程与进程安全:告别日志错乱
这是我最初从`logging`转向`loguru`的一个重要原因。在Python的多线程环境中,标准库的`logging`模块虽然说是线程安全的,但如果你配置不当(比如多个`handler`),或者在某些特定场景下,仍然可能出现日志行交错、内容混乱的问题。而在多进程(例如使用`multiprocessing`库)中,`logging`的问题更明显,如果不使用特殊的队列处理器,日志很可能会丢失。
`loguru`是怎么做的呢?它默认就是**线程安全**的,你不需要做任何特殊处理。对于**多进程**环境,`loguru`提供了一个简单的解决方案:在`add()`方法中设置`enqueue=True`。
```python
logger.add("multi_process.log", enqueue=True)
```
这个`enqueue=True`参数会让`loguru`在内部使用一个线程安全的队列。所有进程的日志消息都先放入这个队列,然后由一个专门的线程负责从队列中取出消息并写入文件。这样就完美避免了多个进程同时写同一个文件可能造成的冲突和数据丢失。我在一个使用`multiprocessing.Pool`进行数据处理的爬虫项目里启用这个参数后,之前偶尔出现的日志丢失现象再也没有发生过。
### 4.2 序列化与异步写入:为了更高的性能
当你需要极高的日志记录性能,或者`sink`目标比较慢时(比如网络存储、数据库),`loguru`提供了异步写入的支持。
```python
# 异步写入文件,提升主程序性能
logger.add("async.log", enqueue=True) # enqueue本身就隐含了异步处理
# 更显式的异步控制,使用 loop 参数(在异步框架中)
import asyncio
logger.add("async_io.log", enqueue=True, loop=asyncio.get_event_loop())
```
设置`enqueue=True`后,日志的格式化、写入等I/O操作就被移交到了后台线程,不会阻塞你的主程序。这对于Web服务器、高频交易系统等对延迟敏感的应用至关重要。实测下来,在大量日志产生的场景下,开启队列后主程序的吞吐量有可观的提升。
### 4.3 结构化日志与外部系统集成
现代运维和监控离不开日志分析平台,比如ELK Stack、Splunk或云服务商的日志服务。这些平台通常更喜欢结构化的数据,比如JSON。`loguru`可以非常轻松地将日志输出为JSON格式。
```python
# 将日志直接序列化为JSON字符串写入文件
logger.add("structured.json", serialize=True)
logger.bind(user_id=12345, action="login").info("用户操作")
```
设置`serialize=True`后,每条日志在写入前都会被转换成JSON字符串。上面那条日志在文件里看起来会是这样的:
```json
{
"text": "用户操作",
"record": {
"elapsed": {"repr": "0.000123", "seconds": 0.000123},
"exception": null,
"extra": {"user_id": 12345, "action": "login"},
"file": {"name": "app.py", "path": "/path/to/app.py"},
"function": "some_function",
"level": {"icon": "ℹ️", "name": "INFO", "no": 20},
"line": 10,
"message": "用户操作",
"module": "app",
"name": "__main__",
"process": {"id": 1234, "name": "MainProcess"},
"thread": {"id": 123456, "name": "MainThread"},
"time": {"repr": "2023-10-27 15:00:00.123456", "timestamp": 1698411600.123456}
}
}
```
这个JSON对象包含了极其丰富的信息,可以直接被Logstash等工具摄取,无需复杂的解析规则。`extra`字段里的自定义上下文(`user_id`, `action`)也原封不动地包含在内,方便后续做聚合分析和查询。这种“开箱即用”的集成能力,大大降低了搭建可观测性系统的门槛。
### 4.4 过滤与动态控制:精细化的日志管理
在生产环境中,我们可能只想记录某个特定级别以上的日志,或者只关心来自某个模块的错误。`loguru`提供了强大的过滤功能。
```python
# 使用 filter 参数进行过滤
def my_filter(record):
# record 是一个包含所有日志信息的字典
# 只记录级别为 ERROR 及以上,并且模块名包含 “payment” 的日志
return record["level"].no >= logger.level("ERROR").no and "payment" in record["name"]
logger.add("important_errors.log", filter=my_filter)
# 更简单的 lambda 表达式
logger.add("warnings_and_above.log", filter=lambda record: record["level"].no >= logger.level("WARNING").no)
```
你还可以在运行时动态地启用或禁用某个`sink`(输出目标),或者临时调整日志级别,这为线上问题调试提供了极大的灵活性。
## 5. 实战指南:将loguru集成到你的项目中
了解了这么多特性,我们来看看如何在一个真实项目(比如一个Flask Web API)中系统地使用`loguru`,替换掉可能正在使用的`logging`。
### 5.1 项目结构规划
假设我们有一个简单的项目结构:
```
my_project/
├── app/
│ ├── __init__.py
│ ├── main.py # Flask应用入口
│ ├── users/
│ │ ├── __init__.py
│ │ └── views.py
│ └── orders/
│ ├── __init__.py
│ └── views.py
├── config.py # 配置文件
└── logs/ # 日志目录(建议.gitignore)
```
### 5.2 创建统一的日志配置模块
我们不希望在每个文件里都重复配置`logger`。最佳实践是在项目入口(如`app/__init__.py`或一个专门的`logger.py`)进行一次全局配置。
```python
# app/__init__.py 或 app/logger.py
import sys
import os
from pathlib import Path
from loguru import logger
# 定义日志目录
LOG_PATH = Path(__file__).parent.parent / "logs"
LOG_PATH.mkdir(exist_ok=True) # 确保目录存在
# 移除loguru默认的handler
logger.remove()
# 1. 控制台输出:开发环境看,信息详细,带颜色
console_format = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<lvl>{level: <8}</lvl> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
"<lvl>{message}</lvl>"
)
logger.add(
sys.stderr,
format=console_format,
level="DEBUG", # 开发时设为DEBUG,生产环境可动态调整
colorize=True,
)
# 2. 通用文件输出:按天分割,保留30天
general_log_path = LOG_PATH / "app_{time:YYYY-MM-DD}.log"
logger.add(
general_log_path,
rotation="00:00", # 每天午夜分割
retention="30 days", # 保留30天
compression="zip", # 自动压缩旧日志
level="INFO",
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}",
enqueue=True, # 确保多进程安全
)
# 3. 错误日志单独输出:所有ERROR及以上日志单独存一个文件,方便监控
error_log_path = LOG_PATH / "error.log"
logger.add(
error_log_path,
rotation="100 MB", # 错误日志按大小分割
retention="10 files", # 只保留最新的10个错误日志文件
level="ERROR",
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}\n{exception}", # 包含异常信息
enqueue=True,
)
# 4. 结构化日志输出(可选,用于接入ELK):JSON格式
if os.getenv("LOG_STRUCTURED", "false").lower() == "true":
json_log_path = LOG_PATH / "app.json.log"
logger.add(
json_log_path,
rotation="500 MB",
serialize=True, # 关键参数,输出为JSON
enqueue=True,
)
# 导出一个配置好的logger,供其他模块使用
# 其他模块只需 `from app.logger import logger` 即可
__all__ = ["logger"]
```
### 5.3 在业务模块中使用
现在,在项目的任何地方,你都可以这样使用:
```python
# app/users/views.py
from flask import request, jsonify
from . import user_bp
from app.logger import logger # 导入我们配置好的logger
# 为这个蓝图/模块创建一个绑定了上下文的logger
user_logger = logger.bind(module="users")
@user_bp.route('/login', methods=['POST'])
def login():
user_logger.info("收到登录请求", username=request.json.get('username'))
try:
# ... 业务逻辑 ...
user_logger.bind(user_id=user.id).info("用户登录成功")
return jsonify({"code": 0})
except AuthenticationError as e:
user_logger.error("用户登录失败: {}", e) # loguru支持类似str.format的格式化
return jsonify({"code": 1, "msg": "认证失败"}), 401
except Exception as e:
user_logger.exception("登录接口发生未预期错误") # 自动记录完整异常堆栈
return jsonify({"code": -1, "msg": "服务器内部错误"}), 500
```
注意这里的使用模式:
1. **模块级logger**:通过`logger.bind(module=“users”)`创建一个带有模块上下文的`logger`实例,这样所有这个模块的日志都自动带有`module=“users”`的标记。
2. **结构化字段**:在打日志时,可以直接以关键字参数的形式添加额外字段(如`username=...`),这些字段会被放入`extra`字典。在文件输出中,如果你配置了JSON序列化,这些字段会完整保留。
3. **安全的字符串格式化**:`loguru`的日志方法支持`{}`占位符,像`str.format()`一样,这比使用`%`格式化或字符串拼接更安全、更现代。`logger.error(“失败: {}”, e)`中的`e`会被自动转换为字符串,避免了可能的异常。
### 5.4 与现有logging模块兼容
如果你的项目已经大量使用了`logging`标准库,或者依赖的第三方库使用`logging`,突然全部换成`loguru`可能不现实。别担心,`loguru`提供了兼容方案。你可以用`loguru`来接管(拦截)所有通过标准`logging`模块输出的日志。
```python
import logging
import sys
from loguru import logger
class InterceptHandler(logging.Handler):
def emit(self, record):
# 获取对应的 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
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
# 配置标准库logging,将所有日志重定向到loguru
logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)
# 现在,所有使用 logging.getLogger(__name__) 的代码,其输出都会由loguru处理
some_third_party_logger = logging.getLogger("some_library")
some_third_party_logger.info("这条信息会通过loguru输出,并遵循loguru的格式和配置")
```
这个`InterceptHandler`是一个桥梁,它捕获所有`logging`模块发出的日志记录,然后使用`loguru`的`logger.log()`方法重新输出。这样,你可以在不修改现有代码和第三方库代码的情况下,统一享受`loguru`的格式化、文件管理、彩色输出等特性。我在迁移一个老项目时用了这个方法,过渡非常平滑。
## 6. 性能优化与避坑指南
用了这么久`loguru`,我也踩过一些坑,总结了一些优化经验,希望能帮你绕过这些弯路。
### 6.1 警惕序列化(serialize)的性能开销
`serialize=True`这个功能很棒,但它是有代价的。将每一条日志记录都转换成JSON字符串,会比纯文本格式化消耗更多的CPU。在我做的一个压力测试中,对于每秒产生上万条日志的高频场景,开启序列化后,日志写入的吞吐量下降了约15%-20%。
**建议**:只在需要的时候开启。例如,通过环境变量控制,在开发环境关闭,在生产环境且确实需要接入ELK等系统时才开启。或者,可以为结构化日志单独配置一个`sink`,只让重要的、需要分析的日志走JSON格式。
```python
# 好的做法:按需开启
import os
if os.getenv("ENABLE_JSON_LOG"):
logger.add("app.json.log", serialize=True, level="INFO", filter=lambda r: r["level"].no >= logger.level("INFO").no)
```
### 6.2 合理使用enqueue参数
`enqueue=True`对于保证多进程安全和提升I/O性能很重要,但它意味着日志消息要先进入内存队列。在极端情况下,如果日志产生速度远远超过写入速度(比如`sink`是一个非常慢的网络磁盘),队列可能会积压,消耗大量内存。
**建议**:
1. 对于文件输出,本地SSD通常很快,`enqueue=True`的开销很小,收益明显,建议开启。
2. 如果`sink`是网络请求(比如自定义函数发往日志平台),务必开启`enqueue=True`,并考虑设置`queue`的大小或使用异步`sink`。
3. 监控你的应用内存。如果发现内存持续增长,可以检查是否是日志队列积压。
### 6.3 格式化字符串的复杂度影响
虽然`loguru`的格式化很灵活,但过于复杂的格式化字符串也会带来微小的性能损耗。比如在`format`中频繁调用自定义函数或进行复杂的字符串操作。
**建议**:保持格式化字符串简洁。对于需要复杂处理的字段,考虑在记录日志前就处理好。`loguru`的`record`解析本身已经很快,瓶颈通常不在这里,但在追求极致性能的场景下可以留意。
### 6.4 避免重复添加和移除Handler
不要在每次函数调用时都动态地`add`和`remove` `sink`。`logger.add()`操作本身有一定开销,而且可能导致文件描述符泄露或日志重复。
**建议**:像前面实战指南那样,在程序初始化时一次性配置好所有的`sink`。如果确实需要动态调整(比如根据配置热加载),确保有正确的清理逻辑,可以使用`logger.remove(handler_id)`来移除特定的`sink`(`add`方法会返回一个唯一的`handler_id`)。
### 6.5 处理日志记录失败本身
如果磁盘满了,或者网络日志服务不可用,日志记录本身就会失败。`loguru`允许你为每个`sink`设置一个错误处理函数。
```python
def sink_error_callback(message):
# 当日志写入失败时,这个函数会被调用
# message 是一个 LogRecord 对象
# 你可以在这里发警报,或者将错误记录到另一个地方
print(f"日志写入失败: {message.record['message']}", file=sys.__stderr__)
logger.add("important.log", catch=True, enqueue=True) # catch参数可以捕获写入时的异常,防止程序崩溃
# 更细粒度的错误处理可以通过自定义sink函数实现
```
设置`catch=True`可以防止因为日志写入异常而导致你的主程序崩溃。对于关键业务日志,你还可以在错误回调里触发告警。
踩过这些坑之后,我的体会是,`loguru`的默认配置和常见用法对大多数项目来说已经足够稳健和高效。上述优化点更多是针对特定高压场景的微调。对于刚接触的开发者,我建议先采用第5章实战指南中的配置,它已经在功能、性能和安全性上取得了一个很好的平衡,能够覆盖90%以上的应用场景。等到项目规模扩大,出现特定的性能瓶颈时,再根据实际情况进行针对性优化。