在Python生态中,日志记录一直是个让人又爱又恨的话题。标准库logging模块虽然功能强大,但配置复杂度常常让开发者望而生畏。我至今记得第一次尝试配置logging时的场景:需要同时处理Logger、Handler、Formatter、Filter等多个组件,光是理解它们之间的关系就花了大半天时间。
Loguru的出现彻底改变了这种局面。这个第三方库的核心设计哲学是"约定优于配置" - 它通过合理的默认值消除了90%的样板代码。举个例子,在标准库中要实现一个简单的文件日志记录,你需要这样写:
python复制import logging
logger = logging.getLogger(__name__)
handler = logging.FileHandler('app.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
而用Loguru只需要一行:
python复制from loguru import logger
logger.add("app.log")
这种简洁性在快速原型开发中尤其宝贵。但Loguru并非只是简化了配置,它在功能上也有很多创新:
提示:虽然Loguru简化了配置,但在大型项目中仍建议在入口处统一配置日志,避免散落在代码各处。
sink参数是add方法的核心,它决定了日志的输出目的地。新手常误以为sink只能是文件路径,实际上它的灵活性超乎想象:
一个实用的技巧是使用函数作为sink,这允许我们实现自定义的日志处理逻辑。比如发送日志到Slack:
python复制import requests
def slack_sink(message):
webhook_url = "https://hooks.slack.com/services/..."
requests.post(webhook_url, json={"text": message.record["message"]})
logger.add(slack_sink, level="ERROR")
处理文件日志时,三个参数配合使用可以构建完整的生命周期管理:
rotation:控制何时创建新文件
retention:控制历史文件保留
compression:节省存储空间
实测案例:我们一个日活百万的应用配置如下:
python复制logger.add(
"logs/app_{time:YYYY-MM-DD}.log",
rotation="100 MB",
retention="15 days",
compression="zip",
enqueue=True
)
这样配置后,系统会自动:
format参数支持极强的自定义能力,几个实用技巧:
颜色标记:使用
python复制format="<green>{time}</green> <level>{message}</level>"
异常信息:{exception}字段
python复制format="{time} | {level} | {message} | {exception}"
调用上下文:
python复制format="{function}@{line} - {message}"
我常用的生产环境格式:
python复制format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | " \
"<level>{level: <8}</level> | " \
"<cyan>{process.name}</cyan>:<cyan>{thread.name}</cyan> | " \
"<cyan>{module}</cyan>.<cyan>{function}</cyan>:<cyan>{line}</cyan> | " \
"<level>{message}</level>"
现代日志系统越来越倾向于结构化日志(如JSON),Loguru通过serialize参数原生支持:
python复制logger.add("app.json", serialize=True)
输出示例:
json复制{
"text": "User logged in",
"record": {
"elapsed": {"repr": "3.1415s", "seconds": 3.1415},
"exception": null,
"extra": {"user_id": 42},
"file": {"name": "app.py", "path": "/path/to/app.py"},
"function": "login",
"level": {"icon": "ℹ️", "name": "INFO", "no": 20},
"line": 173,
"message": "User logged in",
"module": "app",
"name": "__main__",
"process": {"id": 123, "name": "MainProcess"},
"thread": {"id": 456, "name": "MainThread"},
"time": {"repr": "2024-03-20 14:28:32.123456", "timestamp": 1710934112.123456}
}
}
Loguru提供了两种异常记录方式,各有适用场景:
python复制@logger.catch
def risky_function():
...
python复制with logger.catch(message="API调用异常"):
response = call_external_api()
实测发现装饰器模式会增加约5%的函数调用开销,对性能敏感的场景建议使用上下文管理器。
在微服务架构中,通过bind实现请求链路的追踪:
python复制from fastapi import Request
@app.middleware("http")
async def add_logging_context(request: Request, call_next):
request_id = request.headers.get("X-Request-ID", "unknown")
logger_ctx = logger.bind(request_id=request_id)
request.state.logger = logger_ctx
response = await call_next(request)
return response
# 在路由中使用
@app.get("/items/{id}")
async def read_item(id: str, request: Request):
request.state.logger.info(f"Fetching item {id}")
...
这样所有日志都会自动携带request_id,方便后续分析。
虽然Loguru默认线程安全,但在多进程场景需要特殊处理。以下是经过验证的方案:
python复制import multiprocessing
from loguru import logger
def worker():
logger.info("Child process message")
if __name__ == "__main__":
logger.add("app.log", enqueue=True) # 关键参数
processes = []
for _ in range(4):
p = multiprocessing.Process(target=worker)
processes.append(p)
p.start()
for p in processes:
p.join()
enqueue=True会创建一个后台线程专门处理日志写入,避免多进程竞争文件锁。
问题1:日志文件没有按预期轮转
问题2:日志中出现乱码
问题3:性能瓶颈
python复制logger.debug("Big data: {}", lambda: expensive_serialization(data))
我们对比了不同场景下的日志记录耗时(单位μs):
| 操作 | logging模块 | Loguru基本 | Loguru(异步) |
|---|---|---|---|
| 控制台输出 | 45 | 38 | 12 |
| 文件写入 | 78 | 65 | 15 |
| 异常记录 | 120 | 85 | 30 |
| 上下文绑定 | N/A | 5 | 5 |
测试环境:Python 3.9, MacBook Pro M1。可以看到异步模式下Loguru性能优势明显。
在Django项目中推荐这样配置:
python复制from loguru import logger
import sys
LOGGING = None # 禁用Django默认日志
logger.remove() # 移除默认handler
logger.add(
sys.stderr,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{module}</cyan>.<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
"<level>{message}</level>"
)
logger.add(
"logs/django.log",
rotation="100 MB",
retention="7 days",
compression="zip",
enqueue=True
)
python复制class LoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.logger = logger.bind(
ip=request.META.get('REMOTE_ADDR'),
user=request.user.username if request.user.is_authenticated else 'anonymous'
)
return self.get_response(request)
对于数据分析/机器学习项目,推荐配置:
python复制logger.add(
"notebook.log",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
filter=lambda record: "ipykernel" not in record["extra"],
level="DEBUG"
)
# 在Jupyter中使用
logger.info("Feature matrix shape: {}", df.shape)
logger.debug("Column stats:\n{}", df.describe())
特别处理:
Loguru的简洁API背后是精心的设计:
这种设计实现了配置与使用的分离,开发者只需关注日志内容本身。
虽然Loguru可以完全替代标准logging,但在需要与现有代码集成时:
python复制import logging
from loguru import logger
class InterceptHandler(logging.Handler):
def emit(self, record):
logger_opt = logger.opt(depth=6, exception=record.exc_info)
logger_opt.log(record.levelname, record.getMessage())
logging.basicConfig(handlers=[InterceptHandler()], level=0)
这样所有通过标准logging模块记录的日志都会路由到Loguru。
通过sink参数可以实现强大的扩展:
python复制import sqlite3
def db_sink(message):
conn = sqlite3.connect("logs.db")
conn.execute(
"INSERT INTO logs (time, level, message) VALUES (?, ?, ?)",
(message.record["time"], message.record["level"], message.record["message"])
)
conn.commit()
conn.close()
logger.add(db_sink)
python复制import smtplib
from email.mime.text import MIMEText
def email_sink(message):
if message.record["level"].no >= logger.level("ERROR").no:
msg = MIMEText(message.record["message"])
msg["Subject"] = f"Error Alert: {message.record['message'][:50]}"
msg["From"] = "alerts@example.com"
msg["To"] = "admin@example.com"
with smtplib.SMTP("smtp.example.com") as server:
server.send_message(msg)
logger.add(email_sink, level="ERROR")
在Docker环境中推荐以下配置:
dockerfile复制# 日志卷确保持久化
VOLUME /app/logs
# 启动脚本中设置日志环境变量
ENV LOGURU_LEVEL=INFO
ENV LOGURU_FORMAT="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
在Kubernetes中,可以通过sidecar容器收集日志:
yaml复制containers:
- name: app
volumeMounts:
- name: logs
mountPath: /app/logs
- name: log-collector
image: fluentd
volumeMounts:
- name: logs
mountPath: /app/logs
python复制from prometheus_client import Counter
LOG_COUNTER = Counter("app_log_messages", "Log messages count", ["level"])
def prometheus_sink(message):
LOG_COUNTER.labels(level=message.record["level"].name).inc()
logger.add(prometheus_sink)
python复制def sanitize_sink(message):
message = message.replace(api_key, "***")
return message
logger.add(sanitize_sink)
python复制import os
os.chmod("app.log", 0o640) # 仅限用户和组读写
Loguru的成功不仅在于功能,更在于其API设计哲学:
这些原则同样适用于我们设计自己的库和API。比如在实现配置系统时,可以借鉴:
python复制config = Config() \
.set_defaults({"debug": False}) \
.add_file("config.yaml") \
.add_env_vars() \
.bind(context="production")
这种设计模式既保持了灵活性,又降低了使用门槛。