83 lines
2.4 KiB
Python
83 lines
2.4 KiB
Python
"""将 loguru 与标准 logging 桥接,使 uvicorn / FastAPI / Starlette 日志走同一套格式。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
import os
|
||
import sys
|
||
from typing import Any, Optional, Union
|
||
|
||
from loguru import logger
|
||
|
||
_loguru_sink_configured = False
|
||
|
||
|
||
def format_json_pretty(
|
||
data: Any,
|
||
*,
|
||
indent: int = 2,
|
||
max_chars: Optional[int] = 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 = logger.level(record.levelname).name # type: Union[str, int]
|
||
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
|