日志系统架构
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 Worker | 30 天 |
watcher/ | 告警检测全量/错误日志 | Watcher Worker | 30 天 |
report/ | 报告生成全量/错误日志 | Report Worker | 30 天 |
datacenter-etl/ | 数据中心指标同步全量/错误日志 | Datacenter ETL Worker | 30 天 |
dashboard-warmup/ | Dashboard 缓存预热全量/错误日志 | Dashboard Warmup Worker | 30 天 |
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 Server | 30 天 |
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_LEVEL | INFO | DEBUG | INFO 或 WARN |
LOG_DIR | log | log(相对路径,开发);/app/log(Docker) | /log(绝对路径) |
3.3 进程标识与目录映射
每个进程通过 process_name 参数决定日志写入哪个子目录:
| 进程类型 | process_name | 写入目录 | 日志文件 |
|---|---|---|---|
| FastAPI 主进程 | None | app/ | 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 关键规则
- 每个进程只调用一次
setup_logging()— 在 Worker 入口处调用,传入对应的process_name - Task 函数禁止调用
setup_logging()—worker/tasks/*.py中的任务函数运行在宿主 Worker 进程内,日志配置已由宿主进程完成 - 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/auth | POST | USER_LOGIN |
/api/v1/access/logout | POST | USER_LOGOUT |
/api/v1/query/export | POST | EXPORT_PERFORMED |
/api/v1/alert/rules | POST | ALERT_RULE_CREATED |
/api/v1/alert/rules/{id} | PUT | ALERT_RULE_UPDATED |
/api/v1/alert/rules/{id} | DELETE | ALERT_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 日志文件未生成
- 确认
LOG_DIR环境变量设置正确 - 检查目录权限(Docker 中确保挂载点存在)
- 查看控制台输出是否有错误
- 确认
process_name正确传入setup_logging() - 确认对应子目录在
_create_log_directories()的创建列表中(event-analysis不在其中,其日志写入app/目录)
13.2 多进程日志冲突(PermissionError)
现象: Windows 下出现 PermissionError: [WinError 32]
根因: 多个进程的 sink 指向同一日志文件,轮转时 os.rename 失败
排查:
- 检查是否有 task 函数内调用了
setup_logging()— 这会注册多余 sink - 检查所有进程的
process_name是否正确,并确认app/agent/audit文件名带<process>-<pid>后缀 - 确认
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.py | Prometheus 指标定义 |
server/app/core/middleware/audit.py | 审计日志中间件 |
server/app/main.py | RequestLogger / Metrics 中间件注册 |
server/app/api/v1/system/frontend_logs.py | 前端日志采集 API |
server/scripts/dev/logs.py | 后端日志查询 CLI 工具 |
server/worker/runtime/base_worker.py | Worker 基类(统一日志初始化入口) |
server/worker/runners/watcher.py | Watcher Worker 入口 |
server/worker/runners/report.py | Report Worker 入口 |
server/worker/runners/datacenter_etl.py | Datacenter ETL Worker 入口 |
server/worker/runners/dashboard_warmup.py | Dashboard Warmup Worker 入口 |
server/worker/runners/event_analysis.py | Event Analysis Worker 入口 |
server/mcp_server/app.py | MCP Server 入口 |