- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions, schedules, executions, team_members) and unbind goals/tasks before delete - Remove hardcoded personality templates in Android, replace with dynamic system prompt generation from name + description - Set promptSectionsEnabled=false to bypass PromptComposer for personality - Add Tencent Cloud Linux deployment guide (Docker Compose) - Accumulated backend service updates, frontend UI fixes, Android app changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
"""
|
||
JSON 结构化日志配置 — 用于 ELK 日志聚合。
|
||
|
||
用法: 在 main.py 启动时调用 setup_json_logging() 即可。
|
||
会在 logs/ 目录下并行输出 app.json.log(JSON 格式)。
|
||
现有文本格式日志不受影响。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
import os
|
||
from datetime import datetime, timezone
|
||
from logging.handlers import RotatingFileHandler
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from app.core.config import settings
|
||
|
||
|
||
class JsonFormatter(logging.Formatter):
|
||
"""将日志记录格式化为单行 JSON,便于 Filebeat → Elasticsearch 采集。"""
|
||
|
||
def format(self, record: logging.LogRecord) -> str:
|
||
log_entry: dict[str, Any] = {
|
||
"timestamp": datetime.fromtimestamp(
|
||
record.created, tz=timezone.utc
|
||
).isoformat(),
|
||
"level": record.levelname,
|
||
"logger": record.name,
|
||
"message": record.getMessage(),
|
||
"module": record.module,
|
||
"function": record.funcName,
|
||
"line": record.lineno,
|
||
}
|
||
|
||
# 异常信息
|
||
if record.exc_info and record.exc_info[1]:
|
||
log_entry["exception"] = self.formatException(record.exc_info)
|
||
|
||
# 上下文字段(如 request_id / user_id)
|
||
for key in ("request_id", "user_id", "workspace_id", "client_ip", "method", "path", "status_code", "duration_ms"):
|
||
val = getattr(record, key, None)
|
||
if val is not None:
|
||
log_entry[key] = val
|
||
|
||
return json.dumps(log_entry, ensure_ascii=False, default=str)
|
||
|
||
|
||
def setup_json_logging() -> None:
|
||
"""为 root logger 添加 JSON 格式的 RotatingFileHandler。
|
||
|
||
日志写入 LOG_DIR/app.json.log,大小达到 LOG_MAX_BYTES 时轮转。
|
||
"""
|
||
log_dir = Path(settings.LOG_DIR)
|
||
log_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
json_log_path = log_dir / "app.json.log"
|
||
|
||
# 避免重复添加(uvicorn reload 时会重新执行 startup)
|
||
root = logging.getLogger()
|
||
for h in root.handlers:
|
||
if isinstance(h, RotatingFileHandler) and str(json_log_path) in getattr(h, 'baseFilename', ''):
|
||
return
|
||
|
||
handler = RotatingFileHandler(
|
||
json_log_path,
|
||
maxBytes=settings.LOG_MAX_BYTES,
|
||
backupCount=settings.LOG_BACKUP_COUNT,
|
||
encoding="utf-8",
|
||
)
|
||
handler.setFormatter(JsonFormatter())
|
||
handler.setLevel(getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO))
|
||
|
||
root.addHandler(handler)
|
||
logging.getLogger(__name__).info("JSON 日志已启用 → %s", json_log_path)
|