Files
FishServer/fish_api/app/logging_config.py

83 lines
2.4 KiB
Python
Raw Normal View History

"""将 loguru 与标准 logging 桥接,使 uvicorn / FastAPI / Starlette 日志走同一套格式。"""
from __future__ import annotations
import json
import logging
import os
import sys
from typing import Any
from loguru import logger
_loguru_sink_configured = False
def format_json_pretty(
data: Any,
*,
indent: int = 2,
max_chars: int | None = 24_000,
) -> str:
"""将对象格式化为带缩进、保留中文的 JSON 字符串,供 loguru 多行输出。
``max_chars`` 用于避免单条日志过大超长时截断并标注
"""
try:
s = json.dumps(data, ensure_ascii=False, indent=indent, default=str)
except (TypeError, ValueError):
return repr(data)
if max_chars is not None and len(s) > max_chars:
return s[:max_chars] + "\n... (truncated, max_chars=" + str(max_chars) + ")"
return s
class InterceptHandler(logging.Handler):
"""将标准 logging 记录转发到 loguru。"""
def emit(self, record: logging.LogRecord) -> None:
try:
level: str | int = logger.level(record.levelname).name
except ValueError:
level = record.levelno
logger.opt(depth=6, exception=record.exc_info).log(level, record.getMessage())
def setup_logging() -> None:
"""配置 loguru sink并把 uvicorn / fastapi 等 logger 接到 loguru。
可在模块 import 时与 lifespan 启动时各调用一次后者用于覆盖 uvicorn 启动后写入的 handler
"""
global _loguru_sink_configured
level = os.environ.get("LOG_LEVEL", "INFO").upper()
if not _loguru_sink_configured:
logger.remove()
logger.add(
sys.stderr,
level=level,
colorize=sys.stderr.isatty(),
format=(
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<level>{message}</level>"
),
)
_loguru_sink_configured = True
intercept = InterceptHandler()
logging.root.handlers = [intercept]
logging.root.setLevel(logging.DEBUG)
for name in (
"uvicorn",
"uvicorn.error",
"uvicorn.access",
"uvicorn.asgi",
"fastapi",
"starlette",
"starlette.requests",
):
lg = logging.getLogger(name)
lg.handlers.clear()
lg.propagate = True