日志: app-server 增加 JSON 文件输出供阿里云 SLS 采集 (#19)
app/core/logging.py: - 新增 RotatingFileHandler 落 logs/app-server.log (此前仅 stdout), 用 JsonFormatter 输出 单行 JSON (time/level/service/logger/func/line/message, 异常栈内嵌不换行)。 - 控制台保持文本 (stdout 行为不变, systemd/run.log 不受影响)。 - setup_logging 加幂等保护 + 清理预置 root handler 防重复输出。 - 支持环境变量 LOG_FILE/LOG_DIR/LOG_JSON_CONSOLE/LOG_SERVICE_NAME (admin 子进程应设独立 LOG_FILE 避免与主进程争抢同一轮转文件)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: pure <pure@192.168.0.104> Reviewed-on: #19
This commit was merged in pull request #19.
This commit is contained in:
+76
-5
@@ -1,20 +1,91 @@
|
||||
"""统一日志配置。
|
||||
|
||||
业务代码用 `logger = logging.getLogger("shagua.xxx")` 即可,本模块在 main.py 启动时调一次。
|
||||
业务代码用 `logger = logging.getLogger("shagua.xxx")` 即可, 本模块在 main.py 启动时调一次。
|
||||
|
||||
- 控制台(stdout): 人类可读文本, 给 systemd / 本地看。
|
||||
- 文件 `logs/app-server.log`: 单行 JSON, 供阿里云 SLS/Logtail 采集(JSON 模式零正则);
|
||||
异常栈作为字段内嵌不换行 → 每条日志一行。
|
||||
- 环境变量:
|
||||
- LOG_JSON_CONSOLE=1 控制台也输出 JSON
|
||||
- LOG_DIR / LOG_FILE 改落盘路径(默认 logs/app-server.log)
|
||||
⚠️ admin 子进程(app.admin.main, 独立进程)应设不同 LOG_FILE, 避免与主进程争抢同一轮转文件
|
||||
- LOG_SERVICE_NAME JSON 里的 service 字段(默认 app-server)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class JsonFormatter(logging.Formatter):
|
||||
"""把 LogRecord 序列化成单行 JSON(SLS/Logtail 友好)。异常栈内嵌为字段, 整条仍是一行。"""
|
||||
def __init__(self, service: str = "app-server"):
|
||||
super().__init__()
|
||||
self.service = service
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
dt = datetime.fromtimestamp(record.created)
|
||||
data = {
|
||||
"time": dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(record.msecs):03d}",
|
||||
"level": record.levelname,
|
||||
"service": self.service,
|
||||
"logger": record.name,
|
||||
"func": record.funcName,
|
||||
"line": record.lineno,
|
||||
"message": record.getMessage(),
|
||||
}
|
||||
if record.exc_info:
|
||||
data["exception"] = self.formatException(record.exc_info)
|
||||
if record.stack_info:
|
||||
data["stack"] = self.formatStack(record.stack_info)
|
||||
return json.dumps(data, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
_CONFIGURED = False
|
||||
|
||||
|
||||
def setup_logging(debug: bool = False) -> None:
|
||||
global _CONFIGURED
|
||||
if _CONFIGURED:
|
||||
return
|
||||
|
||||
level = logging.DEBUG if debug else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
stream=sys.stdout,
|
||||
service = os.getenv("LOG_SERVICE_NAME", "app-server")
|
||||
|
||||
root = logging.getLogger()
|
||||
root.setLevel(level)
|
||||
# 清掉 basicConfig/uvicorn 可能预置的 root handler, 避免重复输出
|
||||
for h in list(root.handlers):
|
||||
root.removeHandler(h)
|
||||
|
||||
# 控制台: 默认文本(systemd/本地看); LOG_JSON_CONSOLE=1 时输出 JSON
|
||||
console = logging.StreamHandler(sys.stdout)
|
||||
if os.getenv("LOG_JSON_CONSOLE") == "1":
|
||||
console.setFormatter(JsonFormatter(service))
|
||||
else:
|
||||
console.setFormatter(
|
||||
logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||
)
|
||||
root.addHandler(console)
|
||||
|
||||
# 文件: 单行 JSON, 供 Logtail 采集(自动轮转, 单文件 10MB, 保留 5 个)
|
||||
log_file = os.getenv("LOG_FILE") or str(
|
||||
Path(os.getenv("LOG_DIR", "logs")) / "app-server.log"
|
||||
)
|
||||
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8",
|
||||
)
|
||||
file_handler.setFormatter(JsonFormatter(service))
|
||||
root.addHandler(file_handler)
|
||||
|
||||
# 第三方库降噪
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
_CONFIGURED = True
|
||||
|
||||
Reference in New Issue
Block a user