Spaces:
Sleeping
Sleeping
chore: upload app/core/logging.py
Browse files- app/core/logging.py +70 -0
app/core/logging.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""结构化日志 (JSON 格式, 便于 LangSmith 解析)."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
import sys
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from app.config import settings
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class JSONFormatter(logging.Formatter):
|
| 12 |
+
"""极简 JSON formatter — 生产用. 开发可切回默认."""
|
| 13 |
+
|
| 14 |
+
def format(self, record: logging.LogRecord) -> str:
|
| 15 |
+
import json
|
| 16 |
+
|
| 17 |
+
payload: dict[str, Any] = {
|
| 18 |
+
"ts": self.formatTime(record, self.datefmt),
|
| 19 |
+
"level": record.levelname,
|
| 20 |
+
"logger": record.name,
|
| 21 |
+
"msg": record.getMessage(),
|
| 22 |
+
}
|
| 23 |
+
if record.exc_info:
|
| 24 |
+
payload["exc"] = self.formatException(record.exc_info)
|
| 25 |
+
# 透传 extra 字段
|
| 26 |
+
for k, v in record.__dict__.items():
|
| 27 |
+
if k in (
|
| 28 |
+
"name", "msg", "args", "levelname", "levelno", "pathname",
|
| 29 |
+
"filename", "module", "exc_info", "exc_text", "stack_info",
|
| 30 |
+
"lineno", "funcName", "created", "msecs", "relativeCreated",
|
| 31 |
+
"thread", "threadName", "processName", "process", "message",
|
| 32 |
+
"taskName",
|
| 33 |
+
):
|
| 34 |
+
continue
|
| 35 |
+
try:
|
| 36 |
+
json.dumps(v)
|
| 37 |
+
payload[k] = v
|
| 38 |
+
except (TypeError, ValueError):
|
| 39 |
+
payload[k] = str(v)
|
| 40 |
+
return json.dumps(payload, ensure_ascii=False)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def setup_logging() -> None:
|
| 44 |
+
"""初始化根 logger. 在 lifespan 启动时调用."""
|
| 45 |
+
root = logging.getLogger()
|
| 46 |
+
root.setLevel(settings.log_level.upper())
|
| 47 |
+
|
| 48 |
+
# 避免重复 handler
|
| 49 |
+
if root.handlers:
|
| 50 |
+
return
|
| 51 |
+
|
| 52 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 53 |
+
if settings.log_level.upper() == "DEBUG":
|
| 54 |
+
handler.setFormatter(
|
| 55 |
+
logging.Formatter(
|
| 56 |
+
"%(asctime)s | %(levelname)-7s | %(name)s | %(message)s",
|
| 57 |
+
datefmt="%H:%M:%S",
|
| 58 |
+
)
|
| 59 |
+
)
|
| 60 |
+
else:
|
| 61 |
+
handler.setFormatter(JSONFormatter())
|
| 62 |
+
root.addHandler(handler)
|
| 63 |
+
|
| 64 |
+
# 降噪第三方库
|
| 65 |
+
for noisy in ("httpx", "httpcore", "openai", "chromadb", "huggingface_hub"):
|
| 66 |
+
logging.getLogger(noisy).setLevel(logging.WARNING)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def get_logger(name: str) -> logging.Logger:
|
| 70 |
+
return logging.getLogger(name)
|