跳到主要内容

日志系统架构

1. 架构概述​

日志系统采用 目录级进程隔离 设计:每个 Worker 进程写入独立子目录,彻底避免多进程轮转时 os.rename 文件锁冲突(Loguru #548、Windows WinError 32)。

1.1 核心设计目标​

目标实现方式
进程隔离每个进程写入独立目录;app/agent/audit 追加 process+pid 文件后缀
并发安全所有文件 sink 启用 enqueue=True,序列化写入
可观测性JSON Lines 格式(serialize=True),便于日志收集与程序化查询
存储效率按天轮转(00:00)+ gzip 压缩 + 30 天保留
分类管理按 module binding 过滤写入专用目录(agent/、audit/)
审计追踪结构化审计日志中间件 + AuditLogger 专用模块,敏感字段自动脱敏

2. 统一目录结构​

2.1 标准目录树​

/log/                           # 日志根目录(生产环境)
├── app/ # FastAPI API 进程日志(按进程隔离)
│ ├── api.<process>-<ts>-<pid>.log # API 请求日志(filter: module=api)
│ └── error.<process>-<ts>-<pid>.log # 错误日志(level >= ERROR)
├── agent/ # AI Agent 日志(按进程隔离)
│ ├── app.<process>-<ts>-<pid>.log # Agent 全量日志(filter: module=agent,所有级别)
│ └── error.<process>-<ts>-<pid>.log # Agent 错误日志(filter: module=agent,仅 ERROR)
├── watcher/ # Watcher Worker 日志
│ ├── worker.log # Watcher 全量日志
│ └── error.log # Watcher 错误日志
├── report/ # Report Worker 日志
│ ├── worker.log # Report 全量日志
│ └── error.log # Report 错误日志
├── datacenter-etl/ # Datacenter ETL Worker 日志
│ ├── worker.log # Datacenter ETL 全量日志
│ └── error.log # Datacenter ETL 错误日志
├── dashboard-warmup/ # Dashboard 缓存预热 Worker 日志
│ ├── worker.log # Dashboard Warmup 全量日志
│ └── error.log # Dashboard Warmup 错误日志
├── etl/ # ETL 同步日志(当前无活跃写入,见 §2.2 说明)
├── audit/ # 审计日志(按进程隔离)
│ └── audit.<process>-<ts>-<pid>.log # 用户操作审计(filter: module=audit)
├── mcp/ # MCP Server 日志
│ └── mcp_sse.log # MCP Server 运行日志
├── frontend/ # 前端上报日志(API 写入)
│ └── frontend-YYYY-MM-DD.log # 按天分割的前端日志
└── nginx/ # Nginx 访问/错误日志(外部管理)
├── access.log
└── error.log

event-analysis Worker 说明:event-analysis 的 process_name="event-analysis" 不在 _WORKER_LOG_DIRS 映射中,因此日志写入 app/ 目录(按进程隔离命名,与 API 主进程共用 app/ 目录)。

2.2 目录用途说明​

目录用途写入进程保留策略
app/API 请求与错误日志,也承载 event-analysis Worker 日志(因 process_name 不在 _WORKER_LOG_DIRS 中)FastAPI 主进程、Event Analysis Worker30 天
watcher/告警检测全量/错误日志Watcher Worker30 天
report/报告生成全量/错误日志Report Worker30 天
datacenter-etl/数据中心指标同步全量/错误日志Datacenter ETL Worker30 天
dashboard-warmup/Dashboard 缓存预热全量/错误日志Dashboard Warmup Worker30 天
etl/ETL 同步日志(预留目录)。当前所有进程 include_etl=False,module=etl 日志无活跃 sink无30 天
agent/AI Agent 全量 + 错误日志(按 module 过滤,文件名按进程隔离)所有进程(filter: module=agent)30 天
audit/安全审计记录(按 module 过滤,文件名按进程隔离)所有进程(filter: module=audit)30 天
mcp/MCP 协议服务日志MCP Server30 天
frontend/前端上报日志API 进程(写文件)手动管理
nginx/Web 服务器日志Nginx(外部管理)30 天

注意:scripts/dev/logs.py 的 LOG_SUBDIRS 包含 event-analysis 但不包含 datacenter-etl 和 dashboard-warmup。查询这两个 Worker 日志需直接查看对应目录下的文件。


3. 技术实现​

3.1 核心配置参数​

# app/core/observability/logging.py

DEFAULT_LOG_DIR = "/log" # 生产环境统一路径
_ROTATION_TIME = "00:00" # 每天零点轮转
_ENQUEUE = True # 序列化写入,防止并发文件句柄冲突
_STARTUP_TS = datetime.now().strftime("%Y%m%d_%H%M%S") # 进程启动时间戳

# 所有文件 sink 统一配置
_add_file_sink(path, level="INFO", filter_fn=None)
# -> serialize=True, rotation="00:00", retention="30 days",
# compression="gz", encoding="utf-8", enqueue=True

3.2 环境变量​

变量名默认值开发环境生产环境
LOG_LEVELINFODEBUGINFO 或 WARN
LOG_DIRloglog(相对路径,开发);/app/log(Docker)/log(绝对路径)

3.3 进程标识与目录映射​

每个进程通过 process_name 参数决定日志写入哪个子目录:

进程类型process_name写入目录日志文件
FastAPI 主进程Noneapp/api.app-<ts>-<pid>.log + error.app-<ts>-<pid>.log
Watcher Worker"watcher"watcher/worker.log + error.log
Report Worker"report"report/worker.log + error.log
Datacenter ETL Worker"datacenter-etl"datacenter-etl/worker.log + error.log
Dashboard Warmup Worker"dashboard-warmup"dashboard-warmup/worker.log + error.log
Event Analysis Worker"event-analysis"app/(见下方说明)按进程隔离命名写入 app/ 目录
MCP Server"mcp_sse"mcp/mcp_sse.log

映射关系在 _WORKER_LOG_DIRS 中定义:

_WORKER_LOG_DIRS = {
"etl": "etl",
"watcher": "watcher",
"report": "report",
"datacenter-etl": "datacenter-etl",
"dashboard-warmup": "dashboard-warmup",
}

event-analysis 特殊说明:"event-analysis" 不在 _WORKER_LOG_DIRS 中且不以 "mcp" 开头,因此 setup_logging() 中落入 else 分支,按主进程逻辑写入 app/ 目录(文件名带进程隔离后缀)。这意味着 Event Analysis Worker 的日志与 API 主进程日志共存于 app/ 下。

3.4 日志路径解析优先级​

setup_logging(log_dir=...) 参数 > LOG_DIR 环境变量 > /log(生产自动检测) > log(开发回退)

4. 进程隔离原理​

4.1 为什么需要目录级隔离​

Loguru 日志轮转使用 os.rename 重命名当前日志文件。在 Windows 上,如果多个进程同时打开同一文件,os.rename 会失败并抛出 PermissionError: [WinError 32]。

旧架构(已废弃): 所有 Worker 写入 app/api.*.log(同一目录、不同文件名后缀),且 task 函数内部重复调用 setup_logging() 注册多余 sink,导致跨进程文件锁冲突。

新架构: 每个 Worker 进程写入独立目录;API 请求日志与模块日志(app/agent/audit)按 process_name + pid 隔离命名,避免跨进程共享同一轮转目标。enqueue=True 进一步确保同一进程内多线程写入的安全性。

4.2 关键规则​

  1. 每个进程只调用一次 setup_logging() — 在 Worker 入口处调用,传入对应的 process_name
  2. Task 函数禁止调用 setup_logging() — worker/tasks/*.py 中的任务函数运行在宿主 Worker 进程内,日志配置已由宿主进程完成
  3. API 与模块级日志(app/agent/audit)按进程隔离文件名 — 通过 logger.bind(module="api") 写入 app/api.<process>-<ts>-<pid>.log,通过 logger.bind(module="agent") 写入 agent/app.<process>-<ts>-<pid>.log(全量)和 agent/error.<process>-<ts>-<pid>.log(错误)

5. 日志中间件与自动埋点​

5.1 RequestLogger Middleware​

app/main.py 中注册的 request_logger_middleware 为每个 HTTP 请求自动记录访问日志:

logger.bind(
module="api",
path=request.url.path,
method=request.method,
status=response.status_code,
latency_ms=latency_ms,
user_id=user_id,
request_id=rid,
).info("request")

写入位置:app/api.<process>-<ts>-<pid>.log(通过 _filter_api 路由)。

5.2 AuditLogMiddleware​

app/core/middleware/audit.py 自动为敏感 API 端点记录结构化审计日志:

路径方法AuditAction
/api/v1/access/authPOSTUSER_LOGIN
/api/v1/access/logoutPOSTUSER_LOGOUT
/api/v1/query/exportPOSTEXPORT_PERFORMED
/api/v1/alert/rulesPOSTALERT_RULE_CREATED
/api/v1/alert/rules/{id}PUTALERT_RULE_UPDATED
/api/v1/alert/rules/{id}DELETEALERT_RULE_DELETED

审计日志通过 audit_logger.log() 写入,自动对 password、api_key、secret、token 等敏感字段脱敏为 ***。

5.3 Prometheus Metrics Middleware​

app/main.py 中的 metrics_middleware 记录 HTTP 请求到 Prometheus:

  • http_requests_total:按 method、endpoint(路由模板路径)、status 计数
  • http_request_duration_seconds:按 method、endpoint 观测耗时

使用路由模板路径(如 /api/v1/users/{id})替代实际 URL,避免高基数 label 导致指标爆炸。


6. 审计日志模块(AuditLogger)​

app/core/observability/audit_logger.py 提供结构化的审计日志记录能力,独立于普通的 logger.bind(module="audit") 低层日志。

6.1 AuditAction 枚举​

class AuditAction(StrEnum):
AGENT_SESSION_CREATED = "agent_session_created"
AGENT_SESSION_DELETED = "agent_session_deleted"
AGENT_SESSION_CLEARED = "agent_session_cleared"
AGENT_TOOL_EXECUTED = "agent_tool_executed"
AGENT_CHAT_MESSAGE = "agent_chat_message"
MCP_TOOL_ACCESSED = "mcp_tool_accessed"
MCP_CONNECTION_ESTABLISHED = "mcp_connection_established"
PERMISSION_DENIED = "permission_denied"
PERMISSION_CHECK_FAILED = "permission_check_failed"
EXPORT_PERFORMED = "export_performed"
QUERY_EXECUTED = "query_executed"
ALERT_RULE_CREATED = "alert_rule_created"
ALERT_RULE_UPDATED = "alert_rule_updated"
ALERT_RULE_DELETED = "alert_rule_deleted"
USER_LOGIN = "user_login"
USER_LOGOUT = "user_logout"
TOKEN_REFRESHED = "token_refreshed"

6.2 便捷方法​

方法用途
log(action, user_id, resource_type, ...)通用审计记录
log_permission_denied(...)权限拒绝记录
log_agent_tool_execution(...)Agent Tool 执行记录
log_mcp_tool_access(...)MCP Tool 访问记录
log_query_execution(...)SQL 查询执行记录(自动截断 SQL 到 200 字符)
log_export(...)数据导出记录

6.3 敏感数据脱敏​

AuditLogger._sanitize_data() 自动将以下字段替换为 ***: password, password_hash, api_key, secret, token, auth_token, access_token, refresh_token

支持嵌套 dict 和 list 中的 dict。


7. 多环境配置​

7.1 开发环境(本地)​

# 目录结构(相对路径 log/)
server/
├── app/
├── worker/
└── log/
├── app/
├── agent/
├── watcher/
├── report/
├── datacenter-etl/
├── dashboard-warmup/
├── etl/ # 当前无活跃写入
├── audit/
├── mcp/
└── frontend/

# 环境变量
LOG_LEVEL=INFO
LOG_DIR=log

7.2 开发环境(Docker Compose)​

Docker Compose 配置位于 deploy/development/docker-compose.yml,所有服务统一挂载日志卷:

# 所有服务共享日志卷挂载
volumes:
- ../../log:/app/log

# 环境变量(所有服务统一)
environment:
- LOG_LEVEL=DEBUG
- LOG_DIR=/app/log

涉及的服务:api、etl-worker、dashboard-warmup-worker、watcher-worker、report-worker、event-analysis-worker、mcp-server。

7.3 容器内目录​

生产环境 Dockerfile(server/Dockerfile):

RUN mkdir -p /log/{app,agent,etl,audit,mcp,watcher,report}

注意:生产 Dockerfile 当前未创建 datacenter-etl、dashboard-warmup、event-analysis、frontend 子目录。这些目录在运行时由 _create_log_directories() 自动创建(datacenter-etl、dashboard-warmup 在创建列表中;event-analysis 不在创建列表中,因其日志写入 app/ 目录)。


8. 代码中使用​

8.1 初始化日志​

from app.core.observability.logging import setup_logging

# FastAPI 主进程(app/main.py)
setup_logging(include_etl=False)

# Watcher Worker(worker/runners/watcher.py)
setup_logging(include_etl=False, process_name="watcher")

# Report Worker(worker/runners/report.py)
setup_logging(include_etl=False, process_name="report")

# Datacenter ETL Worker(worker/runners/datacenter_etl.py)
setup_logging(include_etl=False, process_name="datacenter-etl")

# Dashboard Warmup Worker(worker/runners/dashboard_warmup.py)
setup_logging(include_etl=False, process_name="dashboard-warmup")

# Event Analysis Worker(worker/runners/event_analysis.py)
setup_logging(include_etl=False, process_name="event-analysis")

# MCP Server(mcp_server/app.py)
setup_logging(include_etl=False, process_name="mcp_sse")

所有 Worker 通过 BaseWorker.start() 统一调用 setup_logging(),无需在 runner 中单独调用。

8.2 记录日志​

from loguru import logger

# 普通日志(写入当前进程的 worker.log 或 error.<process>-<ts>-<pid>.log)
logger.info("服务启动完成")
logger.error("数据库连接失败")

# API 请求日志(由 RequestLogger Middleware 自动写入 app/api.<process>-<ts>-<pid>.log)
# 手动补充 context 时使用:
logger.bind(module="api", request_id="req_xxx").info("自定义请求日志")

# 审计日志(写入 audit/audit.<process>-<ts>-<pid>.log)
from app.core.observability.audit_logger import get_audit_logger, AuditAction
audit = get_audit_logger()
audit.log(AuditAction.USER_LOGIN, user_id="u123", resource_type="auth", result="success")

# Agent 日志(全量写入 agent/app.<process>-<ts>-<pid>.log,错误同时写入 agent/error.<process>-<ts>-<pid>.log)
logger.bind(module="agent").info("Agent 开始分析")
logger.bind(module="agent").error("SQL 生成失败")
# 或预先绑定
_agent_log = logger.bind(module="agent")
_agent_log.info("Agent 执行中")
_agent_log.error("Agent 执行异常")

9. 异步任务派发日志​

告警报告与订阅执行任务使用 threading.Thread(daemon=True) 在当前进程内后台执行,不依赖外部任务队列。日志继承宿主 Worker 进程的 sink 配置:

# worker/tasks/watcher.py
def dispatch_alert_report_task(alert_id, rule, metric_data, project_id):
t = threading.Thread(
target=watcher_generate_alert_report_task,
kwargs={...},
daemon=True,
)
t.start()

# app/services/report_execute_task_service.py
def dispatch_execute_subscription_task(task_id, subscription_id, user_id):
t = threading.Thread(
target=execute_subscription_task_job,
kwargs={...},
daemon=True,
)
t.start()

10. 前端日志​

前端通过 API 上报日志,服务端写入 log/frontend/ 目录:

  • 文件命名: frontend-YYYY-MM-DD.log
  • 格式: JSON Lines
  • API 端点: POST /api/v1/frontend-logs/logs(批量)、POST /api/v1/frontend-logs/log(单条)
  • 读取端点: GET /api/v1/frontend-logs/logs、GET /api/v1/frontend-logs/logs/text

11. 日志查询工具​

scripts/dev/logs.py 支持按 request_id、时间、级别、路径、来源目录过滤 JSON Lines 日志:

# 最近 5 分钟 ERROR 日志(默认行为)
uv run python scripts/dev/logs.py

# 按 request_id 追踪一次请求的所有日志
uv run python scripts/dev/logs.py --request-id req_xxx

# 最近 10 分钟 ERROR 日志
uv run python scripts/dev/logs.py --level ERROR --since 10min

# 按接口路径过滤
uv run python scripts/dev/logs.py --path "/api/v1/agent/query" --tail 10

# 按来源目录过滤(支持 app/agent/etl/audit/mcp/watcher/report/frontend/event-analysis)
uv run python scripts/dev/logs.py --source agent --since 30min

# tail -f 模式
uv run python scripts/dev/logs.py -f

注意:logs.py 的 LOG_SUBDIRS 包含 event-analysis,但不包含 datacenter-etl 和 dashboard-warmup。查询 Datacenter ETL 或 Dashboard Warmup 日志需直接查看 log/datacenter-etl/worker.log 或 log/dashboard-warmup/worker.log 文件。


12. 日志格式​

12.1 控制台输出(开发)​

2026-04-15 10:30:45 | INFO     | app.api.dashboard:get_overview:42 - 获取概览数据

12.2 文件输出(JSON Lines)​

{
"text": "获取概览数据",
"record": {
"elapsed": {"repr": "0:00:00.123456", "seconds": 0.123456},
"exception": null,
"extra": {"module": "api", "path": "/api/v1/dashboard/overview", "method": "GET", "status": 200, "latency_ms": 123, "user_id": "u123", "request_id": "req_xxx"},
"file": {"name": "dashboard.py", "path": "/app/app/api/v1/dashboard.py"},
"function": "get_overview",
"level": {"icon": "", "name": "INFO", "no": 20},
"line": 42,
"message": "获取概览数据",
"module": "dashboard",
"name": "app.api.v1.dashboard",
"process": {"id": 1234, "name": "MainProcess"},
"thread": {"id": 5678, "name": "MainThread"},
"time": {"repr": "2026-04-15 10:30:45.123456+08:00", "timestamp": 1740964245.123456}
}
}

13. 问题排查​

13.1 日志文件未生成​

  1. 确认 LOG_DIR 环境变量设置正确
  2. 检查目录权限(Docker 中确保挂载点存在)
  3. 查看控制台输出是否有错误
  4. 确认 process_name 正确传入 setup_logging()
  5. 确认对应子目录在 _create_log_directories() 的创建列表中(event-analysis 不在其中,其日志写入 app/ 目录)

13.2 多进程日志冲突(PermissionError)​

现象: Windows 下出现 PermissionError: [WinError 32] 根因: 多个进程的 sink 指向同一日志文件,轮转时 os.rename 失败 排查:

  1. 检查是否有 task 函数内调用了 setup_logging() — 这会注册多余 sink
  2. 检查所有进程的 process_name 是否正确,并确认 app/agent/audit 文件名带 <process>-<pid> 后缀
  3. 确认 enqueue=True 已启用

13.3 Datacenter ETL / Dashboard Warmup 日志无法通过 logs.py 查询​

现象: uv run python scripts/dev/logs.py --source datacenter-etl 报错 根因: LOG_SUBDIRS 未包含 datacenter-etl 和 dashboard-warmup 解决: 直接查看 log/datacenter-etl/worker.log 或 log/dashboard-warmup/worker.log 文件

13.4 磁盘空间不足​

cd /log
du -sh *
find . -name "*.gz" -mtime +30 -delete

14. 附录​

14.1 相关文件​

文件说明
server/app/core/observability/logging.py日志核心配置(loguru setup)
server/app/core/observability/audit_logger.py结构化审计日志模块
server/app/core/observability/metrics.pyPrometheus 指标定义
server/app/core/middleware/audit.py审计日志中间件
server/app/main.pyRequestLogger / Metrics 中间件注册
server/app/api/v1/system/frontend_logs.py前端日志采集 API
server/scripts/dev/logs.py后端日志查询 CLI 工具
server/worker/runtime/base_worker.pyWorker 基类(统一日志初始化入口)
server/worker/runners/watcher.pyWatcher Worker 入口
server/worker/runners/report.pyReport Worker 入口
server/worker/runners/datacenter_etl.pyDatacenter ETL Worker 入口
server/worker/runners/dashboard_warmup.pyDashboard Warmup Worker 入口
server/worker/runners/event_analysis.pyEvent Analysis Worker 入口
server/mcp_server/app.pyMCP Server 入口
加载评论中...