diff --git a/docs/03-技术架构/日志体系-时序图.mmd b/docs/03-技术架构/日志体系-时序图.mmd new file mode 100644 index 0000000..a482f00 --- /dev/null +++ b/docs/03-技术架构/日志体系-时序图.mmd @@ -0,0 +1,47 @@ +sequenceDiagram + autonumber + participant FE as 前端页面 + participant API as admin.ts + participant RT as FastAPI Router + participant SVC as Service + participant DB as PostgreSQL + participant FS as 日志文件(RUNTIME_LOG_DIR) + + Note over FE,FS: A 配置变更历史(带筛选) + FE->>API: getSystemLogs({config_key, changed_by, from, to, page, page_size}) + API->>RT: GET /api/admin/system-logs?... + RT->>RT: require_admin() + RT->>SVC: get_system_logs(db, filters) + SVC->>DB: SELECT ConfigChangeLog WHERE ... ORDER changed_at DESC + SVC->>DB: JOIN agents 取 changed_by_name + DB-->>SVC: rows + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: B 安全审计日志(复用后端) + FE->>API: getAuditLogs({employee_id, action, resource, from, to, page, page_size}) + API->>RT: GET /api/admin/audit-logs?... + RT->>RT: require_permission(audit_log:read:all) + RT->>SVC: list_audit_logs(db, filters) + SVC->>DB: SELECT AuditLog WHERE ... + DB-->>SVC: rows + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: D 运行期日志(列表) + FE->>API: getRuntimeLogs({level, from, to, keyword, page, page_size}) + API->>RT: GET /api/admin/runtime-logs?... + RT->>RT: require_admin() + RT->>SVC: read_runtime_logs(RUNTIME_LOG_DIR, filters) + SVC->>FS: 读 *.log 逐行 json.loads + FS-->>SVC: 命中行 + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: D 运行期日志(下载) + FE->>API: getRuntimeLogDownload({level, from, to, keyword}) + API->>RT: GET /api/admin/runtime-logs?download=true + RT->>RT: require_admin() + RT->>SVC: build_runtime_log_stream(RUNTIME_LOG_DIR, filters) + SVC->>FS: 读 *.log 过滤行 + RT-->>FE: StreamingResponse(text/plain, attachment) diff --git a/docs/03-技术架构/日志体系-类图.mmd b/docs/03-技术架构/日志体系-类图.mmd new file mode 100644 index 0000000..ff43aa1 --- /dev/null +++ b/docs/03-技术架构/日志体系-类图.mmd @@ -0,0 +1,78 @@ +classDiagram + direction LR + + class ConfigChangeLog { + +String id + +String config_key + +Text old_value + +Text new_value + +String changed_by + +DateTime changed_at + } + class AuditLog { + +String id + +String employee_id + +String action + +String resource + +String resource_id + +JSON details + +String result + +String ip_address + +Text user_agent + +DateTime created_at + } + class RuntimeLogEntry { + +String timestamp + +String level + +String logger + +String message + +String module + +String function + +int line + +String request_id + +String user_id + } + class ConfigHistoryItem { + +String id + +String config_key + +String old_value + +String new_value + +String changed_by + +String changed_by_name + +DateTime changed_at + } + + class AdminApi { + +GET /system-logs + } + class AuditApi { + +GET /audit-logs + } + class RuntimeApi { + +GET /runtime-logs + } + class AdminService { + +get_system_logs(db, config_key, changed_by, from, to, page, page_size) + } + class AuditLogService { + +list_audit_logs(db, employee_id, action, resource, from, to, page, page_size) + } + class RuntimeLogService { + +read_runtime_logs(dir, level, from, to, keyword, page, page_size) + +build_runtime_log_stream(dir, level, from, to, keyword) + } + class RuntimeLogStore { + <> + +RUNTIME_LOG_DIR/*.log + } + + AdminApi ..> AdminService : 调用 + AuditApi ..> AuditLogService : 调用 + RuntimeApi ..> RuntimeLogService : 调用 + + AdminService ..> ConfigChangeLog : SELECT + AuditLogService ..> AuditLog : SELECT + RuntimeLogService ..> RuntimeLogStore : 读文件 + + ConfigHistoryItem <|.. ConfigChangeLog : 映射 + RuntimeLogEntry <|.. RuntimeLogStore : 逐行解析 diff --git a/docs/03-技术架构/日志体系-系统设计.md b/docs/03-技术架构/日志体系-系统设计.md new file mode 100644 index 0000000..41cd677 --- /dev/null +++ b/docs/03-技术架构/日志体系-系统设计.md @@ -0,0 +1,521 @@ +# 管理后台日志体系 — 系统设计 + 任务分解 + +> **阶段**:standard SOP 第二阶段(架构设计,输入给工程师 寇豆码) +> **架构师**:高见远(software-architect)|**日期**:2026-07-09 +> **上游输入**(已 Read 并核对): +> - 产品+设计文档:`docs/04-功能设计/系统日志与审计日志-产品与设计.md`(许清楚) +> - PRD 增补:`docs/02-产品需求/02-产品需求文档PRD-v1.2-20260704.md` §「管理后台日志体系」 +> - 架构增补:`docs/03-技术架构/00-系统架构设计文档-v1.3.md` §7.1.1 日志体系 +> - 运行期日志设计:`docs/08-安全审计/审计报告-安全审计/健康检查+错误码+日志结构化.md` §3 +> - 真实代码:`frontend-admin/src/{components/Sidebar.vue,router/index.ts,views/SystemLogs.vue,api/admin.ts}`、`backend/app/{api/admin_api.py,api/audit_logs.py,api/router.py,services/admin_service.py,services/audit_log_service.py,models/config_change_log.py,models/audit_log.py,models/automation.py,utils/logging_config.py}`、`docker-compose.yml` + +> **设计原则**:沿用现有技术栈,不新建表(A/B/D 均复用既有存储),不引入新 npm/PyPI 依赖;D 后端方案必须可部署、可执行。 + +> **修订记录**:2026-07-09 初稿;同日依工程师(寇豆码)实现反馈修订——(1) 双写边界(决策2)经核查实际不存在,改为「待 team-lead 依用户决策确认」并修正 T5;(2) 补充 `main.py` 对 `setup_logging` 的接线(D 文件日志能力此前未启用);(3) team-lead 确认方案甲后双写已落地,文档据实更新为「已生效」,`json_format` 依 team-lead 指示定为 True(stdout 亦 JSON)。详见 §6.5、§七、§十 #9/#10、§十一 T5、§十二。 + +--- + +## 一、设计要点摘要 + +| 项 | 结论 | +|----|------| +| 技术栈 | 前端 Vue3 + TS + Element Plus + Pinia(沿用);后端 FastAPI + SQLAlchemy(async) + PostgreSQL(沿用)| +| A 配置变更历史 | 复用 `config_change_logs`;后端 `GET /api/admin/system-logs` 增 `config_key/changed_by/from/to` 过滤;前端 SystemLogs.vue 加筛选控件;侧边栏改名「配置变更历史」| +| B 安全审计日志 | **后端已存在** `GET /api/admin/audit-logs`(权限 `audit_log:read:all`,参数 `employee_id/action/resource/from/to/page/page_size`);本次**仅前端新建页面 + API 函数**,后端零改动 | +| C 自动化动作日志 | 本次**不做**管理后台可视化(P2,标注后续)| +| D 运行期结构化日志 | 新建前端页 + 后端 `GET /api/admin/runtime-logs`(级别/时间/关键字筛选 + 下载);**落地方案:应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount**(不挂 docker.sock)| +| 双写边界(决策2) | ✅ **已落地(方案甲)**:`PUT /api/admin/configs/{key}` → `admin_service.update_config` 现**同 DB session 同时写 A(`config_change_logs`)与 B(`audit_logs` 的 `config_change` 事件)**。配置变更同时出现在「配置变更历史」(A) 与「安全审计日志」(B),决策2 双写已真实生效(见 §十 #9、T5)| +| 权限 | A 沿用 `require_admin`(建议后续命名为 `config_log:read`,本迭代不纳入 RBAC 重构);B 沿用 `audit_log:read:all`;D 用 `require_admin` | +| 新增依赖 | 无(前端无新 npm 包;后端用标准库 `pathlib/os/glob` + `logging.RotatingFileHandler` + `fastapi.responses.StreamingResponse`)| + +--- + +## 二、实现方案与框架选型 + +**难点与对策** + +1. **A 增加筛选而不改表/不加索引**:`config_change_logs` 已有 `idx_ccl_config_key`、`idx_ccl_changed_at` 索引;`changed_by` 无索引,但配置变更频率低、数据量小,用 `WHERE changed_by = :v`(或 JOIN agents 按姓名匹配)可接受,短期不新增索引(见「待明确」)。 +2. **B 零后端改动**:后端 `audit_logs.py` 完整可用,前端补齐页面与 `getAuditLogs` 即可。 +3. **D 容器内读日志(核心难点)**:见第六节,采用「应用写文件 + bind mount」而非 docker.sock。 +4. **命名歧义消除**:侧边栏/路由将「系统日志」拆为「配置变更历史(A)」与新增「安全审计日志(B)」「运行期日志(D)」。 + +**架构模式**:前端 视图组件 + API 函数(axios);后端 路由层(FastAPI Router)→ 服务层(service function)→ 数据层(SQLAlchemy / 文件系统)。A/B 走 DB,D 走文件系统。 + +--- + +## 三、文件列表(标注 新增 / 修改) + +### 前端(frontend-admin/) + +| 文件 | 类型 | 说明 | +|------|------|------| +| `src/components/Sidebar.vue` | 修改 | 「监控与数据」分组:将原 `系统日志` 改名为 **配置变更历史**;新增 **安全审计日志**(`/audit-logs`)、**运行期日志**(`/runtime-logs`) 两个入口 | +| `src/router/index.ts` | 修改 | A 路由 `meta.title` → `配置变更历史`;新增 `audit-logs→AuditLogs.vue`、`runtime-logs→RuntimeLogs.vue` | +| `src/views/SystemLogs.vue` | 修改 | A 页:新增「配置键 / 操作人 / 时间」筛选控件,调用 `getSystemLogs` 新参数 | +| `src/views/AuditLogs.vue` | **新增** | B 页:调用 `getAuditLogs`(级别/操作人/动作/资源/时间筛选 + 分页)| +| `src/views/RuntimeLogs.vue` | **新增** | D 页:级别/时间/关键字筛选 + 分页 + 下载按钮 | +| `src/api/admin.ts` | 修改 | `getSystemLogs` 增参;新增 `getAuditLogs` / `getRuntimeLogs` / `getRuntimeLogDownload` | + +### 后端(backend/app/) + +| 文件 | 类型 | 说明 | +|------|------|------| +| `api/admin_api.py` | 修改 | `GET /system-logs` 增加 `config_key/changed_by/from/to` Query 参数并下传 | +| `services/admin_service.py` | 修改 | `get_system_logs` 增加过滤参数与 WHERE 构造(函数位于文件末尾 ~L1689)| +| `api/runtime_logs.py` | **新增** | 路由前缀 `/admin/runtime-logs`:`GET /runtime-logs`(列表 + `download=true` 下载)| +| `services/runtime_log_service.py` | **新增** | 读 `RUNTIME_LOG_DIR` 下 `*.log`,逐行 JSON 解析、按 level/time/keyword 过滤、分页、生成下载流 | +| `utils/logging_config.py` | 修改 | `setup_logging` 支持 `log_dir`,新增 `RotatingFileHandler`(JSON 格式)写入 `RUNTIME_LOG_DIR`,保留 stdout handler | +| `core/config.py` | 修改 | 新增配置项 `RUNTIME_LOG_DIR`(默认 `/app/logs`)| +| `api/router.py` | 修改 | 挂载 `runtime_logs_router`(与 `audit_logs_router` 一致,~L243-244 后追加)| +| `main.py` | 修改 | **接线**:启动处调用 `setup_logging(level=..., log_dir=settings.RUNTIME_LOG_DIR)`(能力已在 `logging_config.py` 就绪,但此前未被调用,不接线则 `/app/logs` 文件不生成、D 页空)| +| `docker-compose.yml` | 修改 | backend 服务 `volumes` 增加 `${RUNTIME_LOG_HOST_DIR:-/var/log/wecom-it-desk}:/app/logs`;`environment` 增加 `RUNTIME_LOG_DIR=/app/logs`、`LOG_FORMAT=json` | + +> 注:`api/audit_logs.py`、`models/audit_log.py`、`services/audit_log_service.py`、`models/config_change_log.py`、`schemas/admin.py`(ConfigHistoryItem) 本次**均不修改**,直接复用。 + +--- + +## 四、接口设计(含请求/响应 JSON 示例) + +> 统一响应体:`{ "code": 0, "message": "success", "data": {...} }`;错误 `code != 0`。 +> 时间参数统一 **ISO8601**(如 `2026-07-09T00:00:00+08:00` 或 `2026-07-09T16:00:00Z`)。 + +### ① A — 配置变更历史(修改已有) + +**请求** +``` +GET /api/admin/system-logs +Query: + page int = 1 + page_size int = 50 (<=200) + config_key string? 配置键精确/模糊匹配 + changed_by string? 操作人 agent_id(前端用坐席下拉取 id 传入) + from string? ISO8601 起始时间(alias from_time) + to string? ISO8601 结束时间(alias to_time) +``` +**响应** +```json +{ + "code": 0, + "message": "success", + "data": { + "items": [ + { + "id": "3f1a...", + "log_type": "config_change", + "config_key": "auto_reply_enabled", + "old_value": "false", + "new_value": "true", + "changed_by": "a-1001", + "changed_by_name": "张明", + "changed_at": "2026-07-09T14:32:10+08:00" + } + ], + "total": 128, + "page": 1, + "page_size": 50 + } +} +``` + +### ② B — 安全审计日志(复用后端,仅前端对接) + +**请求**(后端已支持,无需改动) +``` +GET /api/admin/audit-logs +Query: + employee_id string? + action string? 如 login / config_change / role_change + resource string? 如 agent / system_config / conversation + from string? ISO8601(alias from_time) + to string? ISO8601(alias to_time) + page int = 1 + page_size int = 50 (<=500) +Header/Permission: audit_log:read:all +``` +**响应**(节选) +```json +{ + "code": 0, + "message": "success", + "data": { + "items": [ + { + "id": "9b2c...", + "employee_id": "a-1001", + "action": "config_change", + "resource": "system_config", + "resource_id": "auto_reply_enabled", + "details": { "old": "false", "new": "true", "ip": "10.80.0.5" }, + "result": "success", + "ip_address": "10.80.0.5", + "user_agent": "Mozilla/5.0 ...", + "created_at": "2026-07-09T14:32:10+08:00" + } + ], + "total": 5321, + "page": 1, + "page_size": 50 + } +} +``` + +### ③ D — 运行期结构化日志(新增) + +**列表请求** +``` +GET /api/admin/runtime-logs +Query: + level string? 枚举 DEBUG/INFO/WWARNING/ERROR/CRITICAL(按 >= 阈值语义过滤) + from string? ISO8601 起始时间(按日志 timestamp 字段) + to string? ISO8601 结束时间 + keyword string? 子串匹配(message 或整行,支持 trace_id / employee_id 检索) + page int = 1 + page_size int = 100 (可 20/50/100) + download bool = false 为 true 时返回文件流(忽略分页) +Permission: require_admin +``` +**列表响应** +```json +{ + "code": 0, + "message": "success", + "data": { + "items": [ + { + "timestamp": "2026-07-09T06:32:10Z", + "level": "ERROR", + "logger": "app.api.admin_api", + "message": "request_start: POST /api/admin/configs/...", + "module": "admin_api", + "function": "get_system_logs", + "line": 902, + "request_id": "req-abc-123", + "user_id": "a-1001" + } + ], + "total": 342, + "page": 1, + "page_size": 100 + } +} +``` +**下载响应**(`download=true`) +``` +HTTP 200 +Content-Type: text/plain; charset=utf-8 +Content-Disposition: attachment; filename="runtime-logs-20260709T143200Z.log" +Body: 每行一条原始 JSON 日志(已按筛选条件命中) +``` +> 实现用 `StreamingResponse(iter([...]), media_type="text/plain", headers={...})`,逐块吐出,避免大文件占内存。 + +--- + +## 五、数据模型 / 字段确认 + +| 类 | 落点 | 字段(关键)| 本次是否改 | +|----|------|------|------| +| **A** `ConfigChangeLog` | `config_change_logs` | `config_key, old_value, new_value, changed_by, changed_at` | 不改;复用 `ConfigHistoryItem` 响应 schema | +| **B** `AuditLog` | `audit_logs` | `employee_id, action, resource, resource_id, details, result, ip_address, user_agent, created_at` | 不改 | +| **C** `ActionLog` | `auto_action_logs` | `session_id, event, direction, system, request, response, status, latency_ms, error, created_at` | 不改(本次不暴露)| +| **D** 运行期日志 | **文件系统**(不建表)| 每行 JSON:`timestamp, level, logger, message, module, function, line, [request_id], [user_id], [extra]` | 由 `logging_config.py` 写出 | + +> D 不新建数据库表,符合 PM 决策(D 是应用运行 stdout 日志的「查看」,非新业务数据)。 + +--- + +## 六、D 后端落地方案(重点 · 可部署) + +### 6.1 结论 + +**采用「应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount」方案。** + +- 后端容器内部:扩展 `logging_config.setup_logging`,在现有 stdout handler 之外,新增一个 `RotatingFileHandler`,用已有的 `JSONFormatter` 把结构化日志写入 `RUNTIME_LOG_DIR`(容器内默认 `/app/logs/wecom-it-desk.log`,轮转备份 `-1/-2...`)。 +- docker-compose:把**宿主机目录**(默认 `/var/log/wecom-it-desk`)bind mount 为容器内的 `/app/logs`。 +- `GET /api/admin/runtime-logs`:直接读 `/app/logs/*.log` 文本文件,逐行 `json.loads`,按 `level` / `from~to`(比较 `timestamp`)/ `keyword` 过滤,时间倒序,分页返回;`download=true` 时以 `StreamingResponse` 返回命中行的文本流。 + +### 6.2 为什么不用 docker.sock / 不读 /var/lib/docker + +| 备选方案 | 否决原因 | +|----------|----------| +| 挂载 `/var/run/docker.sock` 调 Docker API 读日志 | ❌ 安全风险(容器逃逸)、可移植性差(NAS/单机不应依赖 Docker API);**本次明确禁止** | +| 读 `/var/lib/docker/containers//*-json.log` | ❌ 路径含随机容器 ID,每次 `compose up` 变化,无法稳定 bind mount;且 json-file 行格式需二次解析 `{"log":...,"stream":...,"time":...}` | +| **应用层写文件 + bind mount**(选定)| ✅ 路径确定、格式可控(复用现有 `JSONFormatter`)、与 stdout 并存(stdout 仍由 docker `json-file` 捕获供 `docker logs`)、零新依赖、跨环境可移植 | + +### 6.3 部署依赖(docker-compose.yml · backend 服务改动) + +```yaml + backend: + environment: + # 已有环境变量保留 ... + - RUNTIME_LOG_DIR=/app/logs # 新增:运行期日志写入目录(容器内) + - LOG_FORMAT=${LOG_FORMAT:-json} # 新增:确保 JSON 输出(D 端点按 JSON 解析) + - LOG_LEVEL=${LOG_LEVEL:-INFO} + volumes: + - backend-uploads:/app/uploads + - ${RUNTIME_LOG_HOST_DIR:-/var/log/wecom-it-desk}:/app/logs # 新增:宿主机日志目录 bind mount +``` + +- 宿主机目录不存在时 compose 自动创建(bind mount 语义)。 +- 轮转:`RotatingFileHandler(maxBytes=20*1024*1024, backupCount=5)`,与现有 docker `logging.max-size: "20m"` 对齐。 +- 本地开发(非 compose,直接 `uvicorn`):`RUNTIME_LOG_DIR` 默认 `/app/logs`;若该目录不存在,**端点需容错**——返回 `{code:非0, message:"日志目录不可读"}` 或空列表,**不抛 500**(见共享知识)。 + +### 6.4 读取逻辑要点(给工程师) + +1. 遍历 `RUNTIME_LOG_DIR` 下所有 `*.log`(含轮转备份),按文件 mtime 倒序合并。 +2. 逐行 `json.loads`;解析失败行跳过(容错)。 +3. `level` 过滤:大小写不敏感,按「**>= 阈值**」语义(选 `ERROR` → 显示 ERROR/CRITICAL;选 `INFO` → 显示 INFO+WARNING+ERROR+CRITICAL;选 `DEBUG` → 全部)。 +4. `from/to`:取每条日志 `timestamp` 字段(现有 `JSONFormatter` 输出 UTC `Z` 格式),解析为 `datetime` 后比较(兼容 `+08:00` 与 `Z`)。 +5. `keyword`:对整行 JSON 文本(或 `message`)做子串匹配,支持 `trace_id` / `employee_id` 检索。 +6. 分页:收集命中行 → 时间倒序 → 切片(`page/page_size`)。**短期限制**:全量读入内存再切片;若单文件超大,后续接 Loki(已在审计报告中规划)。 +7. `download=true`:将命中行原样(每行一条 JSON)写入 `StreamingResponse`,`Content-Disposition` 文件名带时间戳。 + +### 6.5 接线依赖(关键 · 工程师反馈) + +- `logging_config.setup_logging(log_dir=...)` 的 `RotatingFileHandler` 写文件能力**已实现**,但全项目 grep 显示 `setup_logging` 此前**从未被调用**(仅定义),`main.py` 启动走的是 `logging.basicConfig`。 +- **后果**:若不补这一行,`/app/logs/wecom-it-desk.log` 不会由应用生成,D 页阶段内显示空(已有不抛 500 容错)。 +- **修复(已纳入 T4 文件清单)**:在 `backend/app/main.py` 启动初始化处增加一行 + `setup_logging(level=settings.LOG_LEVEL, json_format=True, log_dir=settings.RUNTIME_LOG_DIR)`(team-lead 定稿 `json_format=True`,stdout 亦结构化 JSON)。 + - **formatter 行为(已运行时验证 + team-lead 定稿)**:`setup_logging` 内 console handler 的 formatter 由 `json_format` 决定,而 **file handler 硬编码 `JSONFormatter`、与 `json_format` 无关**(源码注释「始终使用 JSONFormatter,保证日志文件为结构化 JSON 行」)。team-lead 最终指示 `json_format=True`——stdout 亦输出结构化 JSON,对齐审计报告 §3.2 推荐方向;file handler 恒为 JSON 行,`RUNTIME_LOG_DIR` 下文件 D 端点逐行 `json.loads` 正常解析,**D 页不会空**。两种形态对 D 解析均无影响。 +- 该改动为纯接线,能力就绪,风险极低;D 端点的「目录不可读/无文件不 500」容错保留。 + +--- + +## 七、权限设计 + +| 页面/接口 | 权限 | 现状 | 本次 | +|-----------|------|------|------| +| A `GET /api/admin/system-logs` | `require_admin`(admin 角色)| 已有 | 沿用;**建议后续**明确命名为 `config_log:read`(与 B 一并纳入 RBAC 梳理,本迭代不做)| +| B `GET /api/admin/audit-logs` | `audit_log:read:all`(admin/auditor)| 已有(`require_permission`)| 复用,零改动 | +| D `GET /api/admin/runtime-logs` | `require_admin`(admin 角色)| 新增 | 新增端点用 `require_admin` | +| 双写 `PUT /api/admin/configs/{key}` | 已有 | ✅ **已落地 A+B 双写**(`update_config` 同 session 追加 `AuditLog`,`action=config_change`)| 已生效,T5 验收通过 | + +--- + +## 八、依赖包 + +- **前端**:无新增 npm 包(沿用 Element Plus / axios / Pinia)。 +- **后端**:无新增 PyPI 包。仅用标准库 `pathlib / os / glob` + `logging.RotatingFileHandler` + `fastapi.responses.StreamingResponse`。 + +--- + +## 九、共享知识(跨文件约定) + +- **日志级别枚举**:`DEBUG < INFO < WARNING < ERROR < CRITICAL`(D 的 `level` 参数按此序做 >= 阈值过滤)。 +- **时间参数**:统一 **ISO8601**(前端用 `toISOString()` 或本地带时区;后端用 `datetime` 解析,兼容 `Z` 与 `+08:00`)。 +- **统一响应体**:`{ code: int, message: string, data: any }`;成功 `code=0`。 +- **D 日志目录**:统一读 `settings.RUNTIME_LOG_DIR`(默认 `/app/logs`),不在代码里硬编码绝对路径。 +- **D 容错**:日志目录不存在 / 无可读文件时,列表返回空 `items` 或友好错误码,**禁止 500**。 +- **命名一致性**:A 时间参数用 `from/to`(`alias` 注入),与 B 的 `from/to` 命名保持一致(见「待明确」)。 + +--- + +## 十、待明确事项(遗留决策点) + +1. **A 时间参数命名**:PM 文档用 `changed_from/changed_to`,本设计对齐 B 采用 `from/to`(alias 注入,对前端透明)。若坚持 PM 原文命名需回退——**建议采用 `from/to`**。 +2. **A `changed_by` 筛选粒度**:按 `agent_id` 精确匹配(前端坐席下拉取 id);若要支持「按姓名搜」需 JOIN agents 表做 `ILIKE`。本设计采用 agent_id 精确匹配,实现最简。 +3. **A `changed_by` 缺索引**:高数据量下建议后续加 `idx_ccl_changed_by`;本次不强制。 +4. **A 页面权限命名 `config_log:read`**:本迭代不纳入 RBAC 重构,沿用 `require_admin`;列入后续迭代。 +5. **B 待补事件**(敏感数据查询/角色变更/系统配置变更/异常登录 P1,API 调用统计 P2):本次**不实现**,仅保证接口与页面可扩展(后续迭代直接写 `audit_logs` 即可被 B 页展示)。 +6. **C 管理后台可视化**:本次不做(P2),标注后续。 +7. **E 与 B 衔接**:阶段四「会话日志格式规范」落地后,再在 B 补对应审计事件;本次不实现。 +8. **D `level` 语义**:采用「>= 阈值」;若产品要求「精确匹配」需调整(影响前端下拉说明)。 +9. **双写边界(决策2)— 已落地(方案甲)**:实现核查曾显示 `update_config` 仅写 A;经 team-lead 向用户确认「要补」后,工程师已在 `backend/app/services/admin_service.py` 的 `update_config` 中、写 A 之后同 session 追加 `AuditLog`(`action=config_change`、`resource=system_config`、`resource_id=key`、`details={old,new,operator}`、`result=success`)。**决策2 双写现已真实生效**:配置变更同时入 A 与 B,两表均不在此 commit、由调用方统一提交。T5 验收按方案甲通过(见 §十一)。若后续需补 `ip_address/user_agent`,可在服务层注入 Request 上下文,本期留空。 +10. **`setup_logging` 接线(已落地)**:`logging_config.setup_logging(log_dir=...)` 已实现且已在 `main.py` 启动处调用(T4 已含),`RUNTIME_LOG_DIR` 下日志文件正常生成;team-lead 最终指示 `json_format=True`(stdout 亦结构化 JSON,对齐审计报告 §3.2),file handler 恒为 JSON,D 端点可解析。详见 §6.5。 + +--- + +## 十一、任务分解清单(有序 · 含依赖 · 验收要点) + +> 颗粒度按团队主理人指定:T1 侧边栏+路由;T2 A 筛选;T3 B 页;T4 D 页(前后端);T5 双写回归;T6 联调权限。 + +### T1 — 侧边栏重命名 + 路由入口(A 改名、B/D 新增) +- **涉及文件**:`frontend-admin/src/components/Sidebar.vue`、`frontend-admin/src/router/index.ts` +- **依赖**:无 +- **优先级**:P1 +- **验收要点**:① 侧边栏「监控与数据」出现「配置变更历史(A)」「安全审计日志(B)」「运行期日志(D)」三个独立入口,无「系统日志」歧义统称;② 路由 `audit-logs`、`runtime-logs` 已注册且 `meta.title` 正确;③ A 路由 title 改为「配置变更历史」。 + +### T2 — A 页筛选(前端控件 + 后端参数) +- **涉及文件**:`frontend-admin/src/views/SystemLogs.vue`、`frontend-admin/src/api/admin.ts`(改 `getSystemLogs`)、`backend/app/api/admin_api.py`(改 `GET /system-logs`)、`backend/app/services/admin_service.py`(改 `get_system_logs`) +- **依赖**:T1 +- **优先级**:P1 +- **验收要点**:① 前端 A 页有「配置键/操作人/时间」三类筛选控件且与分页联动;② 后端 `GET /api/admin/system-logs` 支持 `config_key/changed_by/from/to` 且过滤正确;③ 空筛选时等价原行为。 + +### T3 — 新建 B 安全审计日志页(前端,复用后端) +- **涉及文件**:`frontend-admin/src/views/AuditLogs.vue`(新增)、`frontend-admin/src/api/admin.ts`(新增 `getAuditLogs`) +- **依赖**:T1 +- **优先级**:P1 +- **验收要点**:① 页面可筛选 `employee_id/action/resource/from/to` 并分页展示;② 调通既有 `GET /api/admin/audit-logs`;③ 非 admin/auditor 角色被拦截(后端已保证)。 + +### T4 — 新建 D 运行期日志页(前端 + 后端,含部署改动) +- **涉及文件**: + - 前端:`frontend-admin/src/views/RuntimeLogs.vue`(新增)、`frontend-admin/src/api/admin.ts`(新增 `getRuntimeLogs`/`getRuntimeLogDownload`) + - 后端:`backend/app/api/runtime_logs.py`(新增)、`backend/app/services/runtime_log_service.py`(新增)、`backend/app/utils/logging_config.py`(改)、`backend/app/core/config.py`(改)、`backend/app/api/router.py`(改,挂载 router)、`backend/app/main.py`(改,启动处调用 `setup_logging(log_dir=settings.RUNTIME_LOG_DIR)` 完成接线) + - 部署:`docker-compose.yml`(backend volumes + env) +- **依赖**:T1 +- **优先级**:P0 +- **验收要点**:① admin 可按级别+时间+关键字筛选查看日志;② 可下载 `.log`/`.txt` 文件流;③ `main.py` 已调用 `setup_logging` 且 `logging_config` 写入 `RUNTIME_LOG_DIR`,docker-compose 挂载生效(容器内 `/app/logs` 实际生成 `.log`);④ 非 admin 调接口返回 403;⑤ 目录不可读时不 500。 + +### T5 — 双写边界处理(决策2 · 已落地方案甲) +- **背景**:实现核查曾确认 `update_config` 仅写 A;team-lead 向用户(宋献)确认「要补」双写,故采用方案甲。 +- **涉及文件**:`backend/app/services/admin_service.py`(`update_config` 写 A 后同 session 追加 `AuditLog` `config_change` 事件,字段对齐既有 `AuditLog` 模型与 `record_audit_log` 约定) +- **依赖**:T2 +- **优先级**:P0 +- **验收要点**:① 修改任一配置项后,`config_change_logs` 与 `audit_logs`(`config_change` 事件) 均新增对应记录;② 两表记录数一致、内容可对应(`old/new/operator` 一致);③ 不在任一侧删除/合并;④ B 页「安全审计日志」可见该配置变更。 +- **状态**:✅ 已落地,工程师 IS_PASS 转 QA 全量测试。 + +### T6 — 联调与权限核对 +- **涉及文件**:全链路(A/B/D 页面 + 接口) +- **依赖**:T2、T3、T4(T5 可并行) +- **优先级**:P0 +- **验收要点**:① A(admin)/B(audit_log:read:all)/D(admin) 权限分别核对通过;② 三个页面在管理后台真实跑通;③ 与 PM 文档 §5 五项决策验收标准逐条对齐。 + +--- + +## 十二、对 PM 文档遗留 5 问的回应 + +1. **D 后端聚合方案定稿** → 已定稿:应用层 JSON 文件日志 + docker-compose 宿主机目录 bind mount(见第六节)。不使用 docker.sock,不读 `/var/lib/docker`。这是短期可执行方案,中期可平滑切 Loki(D 端点抽象出 `RuntimeLogService`,换实现不影响前端)。 +2. **B 待补事件是否纳入本次** → **不建议纳入**。敏感数据查询/角色变更/系统配置变更/异常登录(P1)、API 调用统计(P2) 列入后续迭代;本次仅保证 B 接口与页面可扩展(新事件直接写 `audit_logs` 即被展示)。 +3. **C 是否需管理后台可视化查询接口** → **本次不做**(标注 P2)。`auto_action_logs` 已落表,后续如需可补 `GET /admin/auto-action-logs`(权限待定)。 +4. **A 页面权限命名** → 沿用 `require_admin`;建议后续明确为 `config_log:read` 并与 B 一并纳入 RBAC 梳理,本迭代不实现。 +5. **E 与 B 衔接** → 阶段四「会话日志格式规范」落地后,再在 B 补对应审计事件;本次不实现。 + +> **实现核查补充(2026-07-09,工程师反馈)**: +> - 决策 #2 的「双写」前提经代码核查**不成立**——`update_config` 仅写 A,未写 B(详见 §十 #9)。本设计已据实修正,并列为需 team-lead 向用户确认的阻塞项;原「T5 双写回归(无代码改动)」已改为「T5 双写边界处理(依赖决策)」。 +> - D 文件日志能力 `setup_logging(log_dir=...)` 已实现,但 `main.py` 未调用,需在启动处补一行接线(已纳入 T4 文件清单 + §6.5)。不接线则 D 页空(已有不 500 容错)。 + +--- + +## 附录 A — 类图(Mermaid) + +```mermaid +classDiagram + direction LR + + class ConfigChangeLog { + +String id + +String config_key + +Text old_value + +Text new_value + +String changed_by + +DateTime changed_at + } + class AuditLog { + +String id + +String employee_id + +String action + +String resource + +String resource_id + +JSON details + +String result + +String ip_address + +Text user_agent + +DateTime created_at + } + class RuntimeLogEntry { + +String timestamp + +String level + +String logger + +String message + +String module + +String function + +int line + +String request_id + +String user_id + } + class ConfigHistoryItem { + +String id + +String config_key + +String old_value + +String new_value + +String changed_by + +String changed_by_name + +DateTime changed_at + } + + class AdminApi { + +GET /system-logs + } + class AuditApi { + +GET /audit-logs + } + class RuntimeApi { + +GET /runtime-logs + } + class AdminService { + +get_system_logs(db, config_key, changed_by, from, to, page, page_size) + } + class AuditLogService { + +list_audit_logs(db, employee_id, action, resource, from, to, page, page_size) + } + class RuntimeLogService { + +read_runtime_logs(dir, level, from, to, keyword, page, page_size) + +build_runtime_log_stream(dir, level, from, to, keyword) + } + class RuntimeLogStore { + <> + +RUNTIME_LOG_DIR/*.log + } + + AdminApi ..> AdminService : 调用 + AuditApi ..> AuditLogService : 调用 + RuntimeApi ..> RuntimeLogService : 调用 + + AdminService ..> ConfigChangeLog : SELECT + AuditLogService ..> AuditLog : SELECT + RuntimeLogService ..> RuntimeLogStore : 读文件 + + ConfigHistoryItem <|.. ConfigChangeLog : 映射 + RuntimeLogEntry <|.. RuntimeLogStore : 逐行解析 +``` + +## 附录 B — 时序图(Mermaid) + +```mermaid +sequenceDiagram + autonumber + participant FE as 前端页面 + participant API as admin.ts + participant RT as FastAPI Router + participant SVC as Service + participant DB as PostgreSQL + participant FS as 日志文件(RUNTIME_LOG_DIR) + + Note over FE,FS: A 配置变更历史(带筛选) + FE->>API: getSystemLogs({config_key, changed_by, from, to, page, page_size}) + API->>RT: GET /api/admin/system-logs?... + RT->>RT: require_admin() + RT->>SVC: get_system_logs(db, filters) + SVC->>DB: SELECT ConfigChangeLog WHERE ... ORDER changed_at DESC + SVC->>DB: JOIN agents 取 changed_by_name + DB-->>SVC: rows + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: B 安全审计日志(复用后端) + FE->>API: getAuditLogs({employee_id, action, resource, from, to, page, page_size}) + API->>RT: GET /api/admin/audit-logs?... + RT->>RT: require_permission(audit_log:read:all) + RT->>SVC: list_audit_logs(db, filters) + SVC->>DB: SELECT AuditLog WHERE ... + DB-->>SVC: rows + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: D 运行期日志(列表) + FE->>API: getRuntimeLogs({level, from, to, keyword, page, page_size}) + API->>RT: GET /api/admin/runtime-logs?... + RT->>RT: require_admin() + RT->>SVC: read_runtime_logs(RUNTIME_LOG_DIR, filters) + SVC->>FS: 读 *.log 逐行 json.loads + FS-->>SVC: 命中行 + SVC-->>RT: {items, total, page, page_size} + RT-->>FE: {code:0, data} + + Note over FE,FS: D 运行期日志(下载) + FE->>API: getRuntimeLogDownload({level, from, to, keyword}) + API->>RT: GET /api/admin/runtime-logs?download=true + RT->>RT: require_admin() + RT->>SVC: build_runtime_log_stream(RUNTIME_LOG_DIR, filters) + SVC->>FS: 读 *.log 过滤行 + RT-->>FE: StreamingResponse(text/plain, attachment) +``` diff --git a/docs/04-功能设计/系统日志与审计日志-产品与设计.md b/docs/04-功能设计/系统日志与审计日志-产品与设计.md new file mode 100644 index 0000000..9039cde --- /dev/null +++ b/docs/04-功能设计/系统日志与审计日志-产品与设计.md @@ -0,0 +1,258 @@ +# 系统日志与审计日志 — 产品 + 设计文档 + +> **版本**:v1.0 | **日期**:2026-07-09 | **作者**:许清楚(产品经理)| **关联文档**:[PRD v1.2](../../02-产品需求/02-产品需求文档PRD-v1.2-20260704.md) / [架构设计 v1.3](../../03-技术架构/00-系统架构设计文档-v1.3.md) / [安全审计报告-日志结构化](../../08-安全审计/审计报告-安全审计/健康检查+错误码+日志结构化.md) / [管理员手册 §10.2](../../05-用户手册/03-管理员手册.md) +> +> **一句话**:管理后台长期存在 5 类「日志」机制概念混淆,本文档统一 A/B/C/D/E 的定义、边界、接口与权限,并落地用户(宋献,税友集团 IT 支持组长)已拍板的 5 项决策。 + +--- + +## 1. 概述:为什么要有这份文档 + +管理后台经代码与文档盘点,确认存在 5 类被笼统称为「日志」的机制,但其**记录对象、落点、读者、用途**各不相同,长期造成概念混淆与维护歧义: + +- **A. 系统日志** = 配置变更历史(记「配置项改了什么值」) +- **B. 操作日志 / 审计日志** = 安全行为事件(记「谁做了什么安全相关动作」) +- **C. 自动化动作日志** = 外部系统调用出入参(排障 + 审计) +- **D. 运行期结构化日志** = 应用运行 stdout 日志(排障) +- **E. 会话 / 消息记录** = 业务主数据(非独立日志子系统) + +其中 A 与 B 最易混淆(都涉及「配置变更」),且当前 A 页面**无任何筛选控件**、D 的「日志查看」在管理员手册中已承诺但**后端接口未落地**。本文档目标: + +1. 给五类日志一个**统一定义与命名**,消除歧义; +2. 明确 **A vs B** 的关键差异与双写边界; +3. 将用户 5 项决策**产品化**为可验收的规格; +4. 作为下一阶段架构师(高见远)的输入,**自包含**地描述接口、字段、权限。 + +--- + +## 2. 五类日志总览表 + +| 编号 | 内部名称 | 管理后台 UI 命名 | 落点(表 / 存储) | 核心用途 | 主要读者 | 实现状态 | +|------|----------|------------------|-------------------|----------|----------|----------| +| **A** | 系统日志(配置变更历史) | **配置变更历史** | `config_change_logs` | 记录配置项前后值 diff | 管理员 / 运维 | 已实现(前端无筛选) | +| **B** | 操作日志 / 审计日志 | **安全审计日志** | `audit_logs` | 记录安全行为事件 | 管理员 / 审计员 | 已实现(部分事件待补) | +| **C** | 自动化动作日志 | 自动化动作日志 | `auto_action_logs` | 外部系统调用出入参全量落表 | 运维 / 开发 | 已实现(落表为主) | +| **D** | 运行期结构化日志 | 运行期日志查看 | stdout → Docker 文件 | 应用运行排障 | 运维 / 开发 | 后端已实现,查看页未落地 | +| **E** | 会话 / 消息记录 | 会话与消息 | `conversations` / `messages` | 业务主数据(会话日志格式规范) | 坐席 / 管理员 | 已实现 | + +> **关系图**:配置变更动作同时写 A 与 B(双写,不收敛);C 记录自动化对外调用;D 是应用层运行日志;E 是业务数据,阶段四对其约定「会话日志格式规范」。 + +```mermaid +flowchart LR + subgraph 写 + PUT[PUT /api/admin/configs/key] -->|双写| A[(A config_change_logs)] + PUT -->|双写 config_change 事件| B[(B audit_logs)] + end + subgraph 落表 + AUTO[自动化动作] --> C[(C auto_action_logs)] + APP[应用运行] -->|JSONFormatter + trace_id| D[(D stdout→Docker文件)] + CHAT[会话/消息] --> E[(E conversations/messages)] + end + subgraph 读-管理后台 + A --> PA[配置变更历史页] + B --> PB[安全审计日志页] + C -. 暂未暴露列表接口 .-> PC[(待定)] + D --> PD[运行期日志查看页-待开发] + E --> PE[会话管理] + end +``` + +--- + +## 3. 逐类详述 + +### 3.1 A — 配置变更历史(系统日志) + +**定义**:系统配置项被修改时自动记录的前后值差异(diff),用于「配置改了什么、谁改的、什么时候改的」可回溯。 + +**使用场景(谁 / 何时 / 为什么)**: +- **谁**:系统管理员(admin)。 +- **何时**:调用 `PUT /api/admin/configs/{key}` 更新任意配置项时,由后端自动写入(该接口注释「更新单个配置项(同时记录变更日志)」)。 +- **为什么**:排查「为什么某功能表现变了」时,快速定位配置被谁在何时改成什么值。 + +**接口**: +| 项目 | 说明 | +|------|------| +| 列表查询 | `GET /api/admin/system-logs`(当前参数 `page` / `page_size`) | +| 写入触发 | `PUT /api/admin/configs/{key}`(admin_api.py 第 117 行)→ 同时写 A 与 B | +| 实现 | `admin_service.get_system_logs` | +| 权限 | admin(沿用 `/api/admin` 鉴权;建议明确为 `config_log:read`) | + +> **决策 3(待开发)**:`GET /api/admin/system-logs` 需补充过滤参数 `config_key`(配置键)、`changed_by`(操作人 agent_id)、`changed_from` / `changed_to`(时间),并在前端 A 页面增加对应筛选控件。 + +**数据模型(JSON Schema 风格)**: + +```json +{ + "ConfigChangeLog": { + "config_key": "string // 配置项键", + "old_value": "string? // 变更前的值", + "new_value": "string? // 变更后的值", + "changed_by": "string // 操作人 agent_id", + "changed_by_name": "string // 操作人姓名", + "changed_at": "datetime // 变更时间" + } +} +``` + +> Schema:`backend/app/schemas/admin.py` 的 `ConfigHistoryItem`;模型:`backend/app/models/config_change_log.py`。前端:`frontend-admin/src/views/SystemLogs.vue`(el-table 五列:时间 / 配置键 / 操作人 / 变更前红色删除线 / 变更后绿色 + 分页 20/50/100)。 + +**UI 命名(决策 1)**:管理后台侧边栏原「系统日志」→ 明确命名为 **「配置变更历史」**,与 B 的「安全审计日志」并列区分。 + +--- + +### 3.2 B — 安全审计日志(操作日志 / 审计日志) + +**定义**:记录系统中与安全相关的行为事件(登录、消息、会话、管理员操作、配置变更等),用于审计与合规追溯。 + +**使用场景(谁 / 何时 / 为什么)**: +- **谁**:管理员、审计员(auditor)。 +- **何时**:关键行为发生时由对应业务代码写入(见下方「已记录事件」)。 +- **为什么**:安全审计、责任追溯、异常行为发现(如异常登录、敏感数据查询)。 + +**接口**: +| 项目 | 说明 | +|------|------| +| 查询 | `GET /admin/audit-logs`(router 前缀 `/admin/audit-logs`) | +| 过滤参数 | `employee_id` / `action` / `resource` / `from` / `to` / `page` / `page_size` | +| 权限 | `audit_log:read:all`(admin / auditor 角色) | +| 实现 | `backend/app/api/audit_logs.py` | + +**数据模型(JSON Schema 风格)**: + +```json +{ + "AuditLog": { + "employee_id": "string // 操作人", + "action": "string // 动作类型,如 login/logout/config_change", + "resource": "string // 资源类型", + "resource_id": "string? // 资源标识", + "details": "object // JSON:前后值 / IP / UA 等", + "result": "enum(success|failure|partial)", + "ip_address": "string // 来源 IP", + "user_agent": "string // UA", + "created_at": "datetime // 发生时间" + } +} +``` + +**已记录事件(安全审计报告 §3)**:登录 / 登出、消息发送、会话创建关闭、管理员操作、配置变更。 +**待补事件**:敏感数据查询(P1)、角色变更(P1)、系统配置变更(P1)、异常登录(P1)、API 调用统计(P2)。 + +**UI 命名(决策 1)**:管理后台侧边栏明确命名为 **「安全审计日志」**,与 A 的「配置变更历史」区分。 + +--- + +### 3.3 C — 自动化动作日志 + +**定义**:自动化流程调用外部系统时的出入参全量落表,用于排障与审计。 + +**使用场景(谁 / 何时 / 为什么)**: +- **谁**:运维 / 开发(排障);审计(追溯外部调用)。 +- **何时**:自动化动作触发外部系统调用(如 `huorong.isolate` 火绒隔离)时。 +- **为什么**:外部调用失败 / 耗时异常时,定位是请求问题还是对方系统问题。 + +**接口 / 落点**: +| 项目 | 说明 | +|------|------| +| 落点 | `auto_action_logs`(模型 `ActionLog`,`backend/app/models/automation.py` 第 249 行) | +| 管理后台列表接口 | **暂未暴露**(当前仅落表);如需可视化查询可后续补充 | + +**数据模型(JSON Schema 风格)**: + +```json +{ + "ActionLog": { + "session_id": "string? // 关联会话", + "action_id": "string? // 自动化动作 ID", + "employee_id": "string? // 触发员工", + "event": "string // 事件,如 huorong.isolate", + "direction": "enum(in|out) // 入/出方向", + "system": "string // 外部系统", + "request": "object // 请求(脱敏)", + "response": "object // 响应(脱敏)", + "status": "string // 状态", + "latency_ms": "int? // 耗时", + "error": "string? // 错误信息" + } +} +``` + +--- + +### 3.4 D — 运行期结构化日志 + +**定义**:应用运行期输出的结构化日志(JSON),承载排障信息,含 `request_id` / `user_id` 上下文。 + +**落点与现状**(日志结构化审计报告 §3): +- 落点:**stdout → Docker 文件**;`JSONFormatter` + 中间件注入 `trace_id`(辅助函数 `log_business` / `log_security`)。 +- 短期:stdout + `docker logs` + `grep`(Docker 收集到 `/var/log/wecom-it-desk/*.log`)。 +- 中长远:Loki + Promtail(中期)/ ELK(长期)。 + +**管理后台「日志查看」**:管理员手册 §10.2 承诺「按时间、级别筛选日志,支持下载日志文件」,**目前无后端接口,未落地**(见决策 4)。 + +**D 页面产品规格(决策 4,详见 §5.1)**:筛选维度 = 级别 + 时间;下载格式 = `.txt` / `.log`;权限 = admin。 + +--- + +### 3.5 E — 会话 / 消息记录 + +**定义**:业务主数据(非独立日志子系统),记录员工与坐席的会话及消息。 + +**落点**:`conversations` / `messages` 表。 +**与日志的关系**:PRD 阶段四「会话日志格式规范」是对其**记录格式**的约定,为后续审计与分析打基础(属于 B 审计的数据来源之一,也支撑阶段四的数据统计看板)。 + +--- + +## 4. 关键辨析:A(记配置值 diff) vs B(记安全行为事件) + +| 维度 | A 配置变更历史 | B 安全审计日志 | +|------|---------------|----------------| +| 记录对象 | 配置项**值 diff**(old_value → new_value) | 安全**行为事件**(action + resource + result) | +| 触发点 | `PUT /api/admin/configs/{key}` | 登录/登出/消息/会话/管理员操作/配置变更等 | +| 字段重心 | `config_key` / `old_value` / `new_value` / `changed_by` / `changed_at` | `action` / `resource` / `result` / `ip_address` / `user_agent` / `details` | +| 核心问题 | 「配置被改成什么了?」 | 「谁做了什么安全动作?结果如何?」 | +| 主要读者 | 管理员(查配置变更) | 管理员 / 审计员(审计追溯) | +| 列表接口 | `GET /api/admin/system-logs`(参数待补) | `GET /admin/audit-logs`(多过滤参数) | +| 权限 | admin | `audit_log:read:all`(admin / auditor) | +| 双写关系 | 同一配置变更动作**同时写 A 与 B**(`config_change` 事件) | 同上 | + +> **结论**:A 与 B 是「同一动作的两个视角」——A 记「值」,B 记「事」。决策 2 明确**维持双写、不收敛**。 + +--- + +## 5. 5 项决策的产品化表述 + 验收标准 + +> 决策来源:用户(宋献)2026-07-09 拍板。本文档严格承袭,未自行增减范围。 + +| # | 决策 | 优先级 | 产品化表述 | 验收标准 | +|---|------|--------|-----------|----------| +| **1** | 命名与归并 | P1 | 管理后台侧边栏「系统日志」明确区分命名:**配置变更历史(A)** vs **安全审计日志(B)** | ① 侧边栏出现两个独立、命名清晰的入口;② 入口标题与本文档 A/B 命名一致,无「系统日志」歧义统称 | +| **2** | 双写边界 | P0 | 配置变更维持 **A+B 双写**,不收敛 | ① `PUT /api/admin/configs/{key}` 同时写 `config_change_logs` 与 `audit_logs` 的 `config_change` 事件;② 不在任一侧删除或合并 | +| **3** | 筛选能力 | P1 | A 页面补充「配置键 / 操作人 / 时间」筛选 | ① 前端 A 页增加三类筛选控件;② 后端 `GET /api/admin/system-logs` 支持 `config_key` / `changed_by` / `changed_from~changed_to` 过滤参数;③ 筛选结果与分页正确联动 | +| **4** | 运行期日志查看 | P0 | 落地 D「按级别 / 时间筛选 + 下载」页面与后端接口 | 见 §5.1 D 页面产品规格表,全部满足 | +| **5** | 文档闭环 | — | 补《系统日志/审计日志 产品+设计文档》,并入 PRD v1.2 与架构 v1.3,列入项目任务 | ① 本文档已建立;② PRD / 架构已增补日志体系小节;③ 状态看板登记 #101–#104 | + +### 5.1 决策 4 — D 运行期日志查看页 产品规格 + +| 规格项 | 定义 | +|--------|------| +| 页面名称 | 运行期日志查看(管理后台侧边栏入口) | +| 权限 | **admin**(仅管理员可访问) | +| 数据来源 | 后端聚合 Docker 容器 stdout 日志(JSONFormatter 输出);短期 = `docker logs` + 过滤;中长远 = Loki / ELK | +| 筛选维度 | **级别**(DEBUG / INFO / WARNING / ERROR / CRITICAL)+ **时间**(from / to) | +| 关键字检索 | 支持按文本关键字检索(如 trace_id / 员工 ID) | +| 下载 | 支持下载筛选后的日志,格式 **`.txt` / `.log`** | +| 分页 / 加载 | 支持分页(默认 100/页,可切 20/50/100) | +| 后端接口(待开发) | `GET /api/admin/runtime-logs`(参数:`level` / `from` / `to` / `keyword` / `page` / `page_size`)+ 下载接口 `GET /api/admin/runtime-logs/download` | +| 验收标准 | ① admin 可筛选级别 + 时间查看日志;② 可下载 `.txt` / `.log`;③ 非 admin 无权限;④ 非 admin 调用接口返回 403 | + +--- + +## 6. 待确认 / 遗留问题 + +1. **D 页后端聚合实现方案**:stdout 文件读取 vs Loki 接入的取舍,需架构师(高见远)在阶段细化时定稿(短期先走 `docker logs` 聚合)。 +2. **B 待补事件排期**:敏感数据查询 / 角色变更 / 系统配置变更 / 异常登录(P1),API 调用统计(P2)——需纳入迭代计划。 +3. **C 是否需管理后台可视化查询接口**:当前仅落表,无列表页;若审计 / 排障需要,后续补充 `GET /admin/auto-action-logs`(权限待定)。 +4. **A 页面权限命名**:建议明确为 `config_log:read`(当前沿用 `/api/admin` 鉴权);可与 B 的 `audit_log:read:all` 一并纳入 RBAC 梳理。 +5. **E 与 B 的衔接**:阶段四「会话日志格式规范」落地后,需确保会话相关行为在 B 中有对应审计事件。 diff --git a/docs/09-部署运维/12-IT服务台业务监控与自愈方案.md b/docs/09-部署运维/12-IT服务台业务监控与自愈方案.md new file mode 100644 index 0000000..1005fab --- /dev/null +++ b/docs/09-部署运维/12-IT服务台业务监控与自愈方案.md @@ -0,0 +1,446 @@ +# IT服务台业务监控与自愈方案 + +> **版本**: v1.0 | **日期**: 2026-07-09 | **维护人**: 宋献 +> **定位**: 业务层监控告警 + 自动化修复方案 + +--- + +## 1. 方案概述 + +### 1.1 背景 + +IT智能服务台上线后,业务异常(如坐席列表获取失败、消息发送失败)直接影响用户体验。当前依赖人工发现和处理,响应慢。 + +### 1.2 目标 + +| 目标 | 指标 | +|-----|------| +| 业务问题早发现 | 5分钟内检测到异常 | +| 自愈能力 | 低级别问题自动修复 | +| 减少人工干预 | 70%业务问题自动化处理 | +| 问题可追溯 | 所有异常都有记录 | + +### 1.3 分层监控架构 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 基础设施层(数据中心兜底) │ +│ - CPU/内存/磁盘/网络/物理服务器 │ +│ - 监控告警:基础设施团队处理 │ +│ - 本系统:仅展示状态 + 记录 │ +└──────────────────────────────────────────────────────────────┘ + ↓ 告知 +┌──────────────────────────────────────────────────────────────┐ +│ 业务应用层(本系统自主维护)⭐ │ +│ - 坐席管理 / 消息通讯 / AI服务 / 会话管理 │ +│ - 检测 → 诊断 → 修复 → 验证 → 记录 │ +└──────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 监控接口设计 + +### 2.1 业务健康检查接口 + +**接口**: `GET /api/admin/health-check` + +**响应示例**: + +```json +{ + "code": 0, + "data": { + "timestamp": "2026-07-09T16:00:00Z", + "overall_status": "healthy", + "infrastructure": { + "nginx": "healthy", + "backend": "healthy", + "redis": "healthy", + "postgres": "healthy" + }, + "business": { + "agents_api": { "status": "healthy", "latency_ms": 45 }, + "message_send": { "status": "healthy", "latency_ms": 120 }, + "wecom_callback": { "status": "healthy", "latency_ms": 30 }, + "ai_rag": { "status": "healthy", "latency_ms": 850 }, + "websocket": { "status": "healthy", "connections": 12 } + }, + "errors": [] + } +} +``` + +### 2.2 监控点清单 + +| 监控点 | 检测方式 | 超时阈值 | 影响级别 | +|-------|---------|---------|---------| +| **nginx** | curl localhost:80 | 2s | 高 | +| **backend** | curl /health | 2s | 高 | +| **redis** | redis-cli ping | 1s | 高 | +| **postgres** | pg_isready | 2s | 高 | +| **agents_api** | GET /api/agents/ | 5s | 高 | +| **message_send** | POST 发送测试消息 | 10s | 高 | +| **wecom_callback** | 企微API ping | 5s | 中 | +| **ai_rag** | POST /api/ai/query | 15s | 中 | +| **websocket** | WS连接数监控 | — | 中 | + +### 2.3 基础设施监控(轻量) + +| 监控项 | 命令 | 告警阈值 | +|-------|------|---------| +| 容器健康 | `docker inspect --format='{{.State.Health.Status}}'` | unhealthy | +| 磁盘使用率 | `docker system df` | > 85% | +| 内存使用 | `docker stats --no-stream` | > 90% | +| 日志大小 | `du -sh /app/logs` | > 1GB | + +> **说明**: 基础设施问题仅告警+记录,解决依赖数据中心团队。 + +--- + +## 3. 规则引擎设计 + +### 3.1 影响级别定义 + +| 级别 | 定义 | 处理方式 | +|-----|------|---------| +| **P0 - 紧急** | 核心业务完全不可用 | 自动修复 + 立即通知 | +| **P1 - 高** | 部分功能受损 | 自动修复 + 通知 | +| **P2 - 中** | 非核心功能异常 | 自动修复 + 记录 | +| **P3 - 低** | 轻微异常,不影响使用 | 记录,择机处理 | + +### 3.2 修复规则库 + +| 规则ID | 触发条件 | 影响级别 | 自动修复 | 通知 | +|--------|---------|---------|---------|------| +| **R001** | agents_api 失败 | P0 | 重启backend容器 | 是 | +| **R002** | message_send 失败 | P0 | 检查企微token/刷新 | 是 | +| **R003** | AI/RAG 超时 | P1 | 重启RAGFlow容器 | 是 | +| **R004** | WebSocket断开 | P1 | 重置连接池 | 是 | +| **R005** | redis 连接超时 | P0 | 重启backend | 是 | +| **R006** | postgres 连接失败 | P0 | 重启backend | 是 | +| **R007** | 磁盘空间不足 | P2 | 清理7天前日志 | 是 | +| **R008** | 容器不健康 | P1 | 重启对应容器 | 是 | + +### 3.3 规则匹配逻辑 + +```python +# 伪代码 +def match_rule(check_result): + for rule in rules: + if rule.trigger == check_result.type and rule.condition(check_result): + return rule + return None + +def process_rule(rule, check_result): + # 评估影响级别 + severity = evaluate_impact(check_result) + + if rule.auto_fix and severity in [P2, P3]: + # 自动修复 + execute_fix(rule.fix_command) + record_audit("AUTO_FIX", rule.id, "success") + elif severity in [P0, P1]: + # 需要通知 + notify_admin(severity, check_result) + record_audit("MANUAL_NEEDED", rule.id, "pending") +``` + +--- + +## 4. 修复脚本库 + +### 4.1 脚本目录结构 + +``` +deploy-scripts/ +├── health-check/ +│ ├── check_all.sh # 全量检查 +│ └── check_business.sh # 业务检查 +├── fixes/ +│ ├── restart_backend.sh # 重启后端 +│ ├── restart_nginx.sh # 重启Nginx +│ ├── restart_redis.sh # 重启Redis +│ ├── clear_logs.sh # 清理日志 +│ ├── refresh_wecom_token.sh # 刷新企微Token +│ └── restart_ragflow.sh # 重启RAGFlow +└── notify/ + └── notify.sh # 企微通知 +``` + +### 4.2 修复脚本示例 + +```bash +# restart_backend.sh - 重启后端容器 +#!/bin/bash +CONTAINER_NAME="wecom_it_backend" + +echo "[$(date)] 重启后端容器: $CONTAINER_NAME" +docker compose restart $CONTAINER_NAME + +# 等待健康检查通过 +for i in {1..30}; do + STATUS=$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER_NAME 2>/dev/null || echo "unknown") + if [ "$STATUS" = "healthy" ]; then + echo "[$(date)] 容器已就绪" + exit 0 + fi + sleep 2 +done + +echo "[$(date)] 容器健康检查超时" +exit 1 +``` + +### 4.3 调用方式(复用 jumpserver-ops) + +```python +# 通过 jumpserver-ops 执行远程修复脚本 +import subprocess + +def execute_fix_script(script_name, server="10.90.5.110"): + """执行远程修复脚本""" + cmd = [ + "python", "jms_ops.py", + "exec", "-c", + f"bash /opt/wecom-it-desk/deploy-scripts/fixes/{script_name}", + "--reuse" + ] + result = subprocess.run(cmd, capture_output=True, text=True) + return result.returncode == 0 +``` + +--- + +## 5. 通知机制 + +### 5.1 企微机器人通知 + +**WebHook 地址**: 通过管理后台配置(已有集成能力) + +**消息模板**: + +```json +{ + "msgtype": "markdown", + "markdown": { + "content": "## 🔴 IT服务台告警\n\n" + + "> **级别**: P0-紧急\n" + + "> **问题**: 坐席列表API获取失败\n" + + "> **时间**: 2026-07-09 16:05:00\n" + + "> **自动修复**: 已执行(重启backend容器)\n\n" + + "> **状态**: 修复中...\n\n" + + "> [查看监控面板](https://itsupport.servyou.com.cn/itadmin/)" + } +} +``` + +### 5.2 通知级别 + +| 级别 | 通知方式 | 通知对象 | +|-----|---------|---------| +| P0 | 企微机器人 + 电话 | 管理员 + 值班人员 | +| P1 | 企微机器人 | 管理员 | +| P2 | 企微机器人 | 管理员 | +| P3 | 仅记录 | — | + +--- + +## 6. 巡检任务计划 + +### 6.1 巡检频率 + +| 任务 | 频率 | 执行方式 | +|-----|------|---------| +| 业务健康检查 | 每5分钟 | 后台定时任务 | +| 基础设施检查 | 每5分钟 | 后台定时任务 | +| 日志清理 | 每天凌晨3点 | Cron | +| 巡检报告 | 每天早上9点 | 定时任务 | + +### 6.2 巡检流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 定时任务触发(每5分钟) │ +└─────────────────────┬───────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 1. 执行 /api/admin/health-check │ +│ 2. 分析最近5分钟错误日志 │ +│ 3. 匹配规则库 │ +└─────────────────────┬───────────────────────────────────────┘ + ▼ + ┌────────────┴────────────┐ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ 有异常 │ │ 全部正常 │ + └──────┬──────┘ └──────┬──────┘ + ▼ ▼ + ┌─────────────┐ ┌─────────────┐ + │ 匹配规则 │ │ 更新最后 │ + │ → 执行修复 │ │ 正常时间 │ + │ → 通知 │ │ → 结束 │ + └─────────────┘ └─────────────┘ +``` + +### 6.3 审计记录 + +所有巡检和修复动作记录到审计日志: + +| 字段 | 说明 | +|-----|------| +| id | 记录ID | +| timestamp | 时间 | +| type | AUTO_FIX / MANUAL_NEEDED / NOTIFY | +| rule_id | 匹配的规则ID | +| check_result | 检测结果 | +| fix_result | 修复结果 | +| notified | 是否通知 | + +--- + +## 7. 管理后台集成 + +### 7.1 监控看板 + +在管理后台新增"系统健康"模块: + +- **实时状态**: 整体健康度指示器(绿/黄/红) +- **各服务状态**: nginx/backend/redis/postgres/AI +- **最近告警**: 最近10条告警记录 +- **巡检历史**: 最近7天巡检结果 + +### 7.2 规则配置 + +管理后台可配置: + +- 启用/禁用某条规则 +- 调整阈值 +- 开启/关闭自动修复 +- 通知人员配置 + +--- + +## 8. 实施计划 + +### 阶段一:基础监控(1-2天) + +- [ ] 实现 `/api/admin/health-check` 接口 +- [ ] 集成 nginx/backend/redis/postgres 检查 +- [ ] 管理后台展示健康状态 + +### 阶段二:业务监控(1-2天) + +- [ ] 坐席列表API监控 +- [ ] 消息发送监控 +- [ ] AI/RAG服务监控 + +### 阶段三:自愈能力(2-3天) + +- [ ] 规则引擎实现 +- [ ] 修复脚本库 +- [ ] 自动执行 + 记录 + +### 阶段四:通知集成(1天) + +- [ ] 企微机器人通知 +- [ ] 告警模板 +- [ ] 通知人员配置 + +### 阶段五:巡检任务(1天) + +- [ ] 定时任务配置 +- [ ] 巡检报告 +- [ ] 审计日志 + +--- + +## 9. 实施记录 + +### 阶段一:基础监控 ✅ (2026-07-09) + +- [x] 实现 `/api/admin/health-check` 接口 +- [x] 集成 nginx/backend/redis/postgres 检查 + +**产出**: +- `backend/app/services/health_check_service.py` - 健康检查服务 +- `backend/app/api/admin_api.py` - 健康检查端点 + +### 阶段二:业务监控 ✅ (2026-07-09) + +- [x] 坐席列表API监控 +- [x] 消息发送监控 +- [x] AI/RAG服务监控 +- [x] WebSocket连接监控 + +### 阶段三:自愈能力 ✅ (2026-07-09) + +- [x] 规则引擎实现(8条规则) +- [x] 修复脚本库 +- [x] 自动执行 + 记录 + +**产出**: +- `deploy-scripts/fixes/` - 修复脚本目录 + - `restart_backend.sh` - 重启后端 + - `restart_redis.sh` - 重启Redis + - `restart_nginx.sh` - 重启Nginx + - `restart_ragflow.sh` - 重启RAGFlow + - `clear_logs.sh` - 清理日志 +- `deploy-scripts/health-check/check_all.sh` - 巡检脚本 +- `deploy-scripts/rules/auto_healer.py` - 规则引擎 + +### 阶段四:通知集成 ✅ (2026-07-09) + +- [x] 企微机器人通知模块 +- [x] 分级通知(P0电话/P1机器人/P2记录) +- [x] 告警模板 + +**产出**: +- `deploy-scripts/notify/wecom_notifier.py` - 企微通知模块 + +### 阶段五:定时任务 ✅ (2026-07-09) + +- [x] 定时巡检任务(每5分钟) +- [x] 每日报告(早上9点) +- [x] 日志清理(凌晨3点) +- [x] Crontab配置 + +**产出**: +- `deploy-scripts/health-check/scheduled_check.py` - 定时巡检脚本 +- `deploy-scripts/health-check/crontab.example` - Crontab配置示例 + +### 文件清单 + +| 文件 | 说明 | +|------|------| +| `backend/app/services/health_check_service.py` | 健康检查核心服务 | +| `backend/app/api/admin_api.py` | 健康检查API端点 | +| `deploy-scripts/fixes/*.sh` | 修复脚本集 | +| `deploy-scripts/health-check/check_all.sh` | 巡检脚本 | +| `deploy-scripts/health-check/scheduled_check.py` | 定时巡检脚本 | +| `deploy-scripts/health-check/crontab.example` | Crontab配置 | +| `deploy-scripts/rules/auto_healer.py` | 规则引擎 | +| `deploy-scripts/notify/wecom_notifier.py` | 企微通知模块 | + +--- + +## 10. 附录 + +### 10.1 相关文档 + +- [00-标准故障排查手册](./00-标准故障排查手册.md) +- [11-堡垒机运维工具](./11-堡垒机运维工具.md) +- [蓝绿部署指南](./蓝绿部署指南.md) + +### 9.2 参考资源 + +- jumpserver-ops 工具: `C:\Users\simon\.workbuddy\skills\jumpserver-ops` +- 故障排查手册 CASE 库 + +--- + +**维护记录** + +| 版本 | 日期 | 变更 | +|------|------|------| +| v1.0 | 2026-07-09 | 初始版本 + 阶段1-5全部实施完成 + 生产部署完成 (10.90.5.110) |