"""
loguru 统一日志配置 + InterceptHandler 拦截标准库 logging。
日志约定:
- INFO:面向运维的稳定摘要,避免敏感字段与高频噪音。
- DEBUG:可记录完整上下文、用户内容、连接串、URL 等敏感信息;仅用于受控环境排查,
生产环境勿长期开启 DEBUG。
由 ``Settings.log_level`` 控制(环境变量 ``LOG_LEVEL``,默认 ``INFO``);
设为 ``DEBUG`` 时上述详细日志才会输出。
"""
import logging
import sys
from loguru import logger
from app.core.config import settings
def _sink_min_level() -> str:
raw = (settings.log_level or "INFO").strip().upper()
if raw in ("TRACE", "DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL"):
return raw
return "INFO"
class InterceptHandler(logging.Handler):
"""Route standard-library logging messages into loguru."""
def emit(self, record: logging.LogRecord) -> None:
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
while frame and 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()
)
def setup_logging() -> None:
"""Call once at process entry (API:`main`;Worker:`celery_app` 在首行调用)。
Celery 需在 `app.tasks.celery_app` 中设置 `worker_hijack_root_logger=False`,否则
会覆盖根 logger,无法与下方 InterceptHandler + loguru 格式对齐。
"""
logger.remove()
logger.add(
sys.stderr,
level=_sink_min_level(),
format=(
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
"{level.name: <8} | "
"{name}:{function}:{line} | "
"{extra[request_id]} | "
"{message}"
),
backtrace=True,
diagnose=False,
)
# 将根 logger 重定向到 loguru,不再使用 basicConfig(文档要求:统一走 loguru)
root = logging.getLogger()
root.handlers = [InterceptHandler()]
root.setLevel(0)
for name in (
"uvicorn",
"uvicorn.error",
"uvicorn.access",
"sqlalchemy.engine",
"celery",
"celery.worker",
):
logging.getLogger(name).handlers = [InterceptHandler()]
logger.configure(extra={"request_id": "-"})
def get_logger(name: str) -> logging.Logger:
"""获取具名 logger,统一走 loguru 拦截。各模块应使用此函数而非直接 import logging。"""
return logging.getLogger(name)