|
| 1 | +# 日志系统设计文档 |
| 2 | + |
| 3 | +> 本文档描述 TapTap MCP Server 和 Proxy 的文件日志系统设计。 |
| 4 | +
|
| 5 | +## 概述 |
| 6 | + |
| 7 | +日志系统支持将运行日志同步写入文件,便于问题排查和审计。Proxy 和 Server 的日志完全独立,各自有独立的配置和存储路径。 |
| 8 | + |
| 9 | +### 输出控制逻辑 |
| 10 | + |
| 11 | +| 配置 | 作用 | |
| 12 | +| ---------------- | ------------------------------------------------------------------- | |
| 13 | +| `logLevel` | 控制哪些级别的日志被输出(到 stderr 和文件) | |
| 14 | +| `verbose` | 影响日志级别(`verbose=true` → `logLevel=debug`)+ 控制额外调试信息 | |
| 15 | +| `logFileEnabled` | 单独控制是否写入文件 | |
| 16 | + |
| 17 | +**输出策略:** |
| 18 | + |
| 19 | +- **stderr**: 总是输出(MCP 标准行为),只受日志级别过滤 |
| 20 | +- **文件**: 由 `logFileEnabled` 控制,只受日志级别过滤 |
| 21 | +- **MCP notification**: 仅在 SSE/HTTP 模式下发送(不在 stdio 模式) |
| 22 | + |
| 23 | +## 架构 |
| 24 | + |
| 25 | +``` |
| 26 | +┌─────────────────────────────────────────────────────────────────┐ |
| 27 | +│ 日志系统架构 │ |
| 28 | +├─────────────────────────────────────────────────────────────────┤ |
| 29 | +│ │ |
| 30 | +│ ┌──────────────────┐ ┌──────────────────┐ │ |
| 31 | +│ │ MCP Server │ │ Proxy │ │ |
| 32 | +│ │ (可能在云端/K8s) │ │ (用户本地运行) │ │ |
| 33 | +│ ├──────────────────┤ ├──────────────────┤ │ |
| 34 | +│ │ Logger 类 │ │ ProxyLogger │ │ |
| 35 | +│ │ ↓ │ │ ↓ │ │ |
| 36 | +│ │ LogWriter │ │ LogWriter │ │ |
| 37 | +│ │ ↓ │ │ ↓ │ │ |
| 38 | +│ │ stderr + 文件 │ │ stderr + 文件 │ │ |
| 39 | +│ └──────────────────┘ └──────────────────┘ │ |
| 40 | +│ │ │ │ |
| 41 | +│ ▼ ▼ │ |
| 42 | +│ /tmp/taptap-mcp/logs/server/ /tmp/taptap-mcp/logs/proxy/ │ |
| 43 | +│ │ |
| 44 | +└─────────────────────────────────────────────────────────────────┘ |
| 45 | +``` |
| 46 | + |
| 47 | +## 目录结构 |
| 48 | + |
| 49 | +``` |
| 50 | +${log_root}/ # 默认: /tmp/taptap-mcp/logs |
| 51 | +├── proxy/ |
| 52 | +│ ├── ${user_id}/ |
| 53 | +│ │ └── ${project_id}/ |
| 54 | +│ │ ├── proxy-2025-01-01.log |
| 55 | +│ │ └── proxy-2025-01-02.log |
| 56 | +│ └── ${kid_hash}/ # 无 user_id/project_id 时使用 |
| 57 | +│ └── proxy-2025-01-01.log |
| 58 | +└── server/ |
| 59 | + ├── ${workspace_hash}/ # stdio 模式 |
| 60 | + │ └── server-2025-01-01.log |
| 61 | + └── server-2025-01-01.log # SSE/HTTP 模式(统一日志) |
| 62 | +``` |
| 63 | + |
| 64 | +### Hash 计算规则 |
| 65 | + |
| 66 | +- **kid_hash**: `SHA256(kid).substring(0, 8)` - 用于无 user_id/project_id 时标识 Proxy |
| 67 | +- **workspace_hash**: `SHA256(path.resolve(workspace_root)).substring(0, 8)` - 用于 Server stdio 模式 |
| 68 | + |
| 69 | +Hash 计算是稳定的,同一输入总是产生相同输出。 |
| 70 | + |
| 71 | +## 配置 |
| 72 | + |
| 73 | +### Proxy 配置 (config.json) |
| 74 | + |
| 75 | +```json |
| 76 | +{ |
| 77 | + "server": { ... }, |
| 78 | + "tenant": { |
| 79 | + "project_path": "/path/to/project", |
| 80 | + "user_id": "12345", |
| 81 | + "project_id": "67890" |
| 82 | + }, |
| 83 | + "auth": { ... }, |
| 84 | + "options": { |
| 85 | + "verbose": false, |
| 86 | + "log": { |
| 87 | + "root": "/tmp/taptap-mcp/logs", |
| 88 | + "enabled": true, |
| 89 | + "level": "info", |
| 90 | + "max_days": 7 |
| 91 | + } |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +| 字段 | 类型 | 默认值 | 说明 | |
| 97 | +| -------------- | -------- | ---------------------- | --------------------------- | |
| 98 | +| `log.root` | string | `/tmp/taptap-mcp/logs` | 日志根目录 | |
| 99 | +| `log.enabled` | boolean | `false` | 是否启用文件日志 | |
| 100 | +| `log.level` | LogLevel | `info` | RFC 5424 日志级别(见下表) | |
| 101 | +| `log.max_days` | number | `7` | 日志保留天数 | |
| 102 | + |
| 103 | +**注意**: 当 `verbose=true` 时,`log.level` 自动变为 `debug`。 |
| 104 | + |
| 105 | +### Server 配置 (环境变量) |
| 106 | + |
| 107 | +| 环境变量 | 默认值 | 说明 | |
| 108 | +| ------------------------- | ---------------------- | ---------------- | |
| 109 | +| `TAPTAP_MCP_LOG_ROOT` | `/tmp/taptap-mcp/logs` | 日志根目录 | |
| 110 | +| `TAPTAP_MCP_LOG_FILE` | `false` | 是否启用文件日志 | |
| 111 | +| `TAPTAP_MCP_LOG_LEVEL` | `info` | 日志级别 | |
| 112 | +| `TAPTAP_MCP_LOG_MAX_DAYS` | `7` | 日志保留天数 | |
| 113 | + |
| 114 | +**注意**: 当 `TAPTAP_MCP_VERBOSE=true` 时,日志级别自动变为 `debug`。 |
| 115 | + |
| 116 | +## 日志级别 (RFC 5424) |
| 117 | + |
| 118 | +使用 RFC 5424 标准的 syslog 日志级别,Server 和 Proxy 共用统一定义: |
| 119 | + |
| 120 | +| 级别 | 优先级 | 说明 | |
| 121 | +| ----------- | ------ | ---------------------- | |
| 122 | +| `emergency` | 0 | 系统不可用 | |
| 123 | +| `alert` | 1 | 必须立即采取行动 | |
| 124 | +| `critical` | 2 | 临界条件 | |
| 125 | +| `error` | 3 | 错误条件 | |
| 126 | +| `warning` | 4 | 警告条件 | |
| 127 | +| `notice` | 5 | 正常但重要的条件 | |
| 128 | +| `info` | 6 | 信息性消息(默认) | |
| 129 | +| `debug` | 7 | 调试级别消息(最详细) | |
| 130 | + |
| 131 | +**优先级规则**: 数字越小优先级越高(越严重)。配置的级别表示"该级别及以上(优先级更高的)"会被写入文件。 |
| 132 | + |
| 133 | +例如 `level=warning` 会写入 warning、error、critical、alert、emergency,但不写入 notice、info、debug。 |
| 134 | + |
| 135 | +**常用配置**: |
| 136 | + |
| 137 | +- 生产环境推荐: `info` 或 `warning` |
| 138 | +- 开发调试: `debug` |
| 139 | +- 仅错误: `error` |
| 140 | + |
| 141 | +## 日志格式 |
| 142 | + |
| 143 | +``` |
| 144 | +[2025-01-01T10:30:00.123Z] [INFO] [server] Tool called: create_leaderboard |
| 145 | +[2025-01-01T10:30:00.456Z] [ERROR] [proxy] Connection failed: ECONNREFUSED |
| 146 | +[2025-01-01T10:30:01.789Z] [DEBUG] [http] HTTP GET https://api.example.com/... |
| 147 | +``` |
| 148 | + |
| 149 | +格式: `[ISO时间戳] [级别] [模块] 消息` |
| 150 | + |
| 151 | +## 日志轮转 |
| 152 | + |
| 153 | +- **按日期轮转**: 每天一个新文件,文件名格式 `{prefix}-YYYY-MM-DD.log` |
| 154 | +- **自动清理**: 超过 `max_days` 的日志文件会被自动删除 |
| 155 | +- **清理时机**: 每次初始化 LogWriter 时执行 |
| 156 | + |
| 157 | +## Docker 部署注意事项 |
| 158 | + |
| 159 | +默认日志路径 `/tmp/taptap-mcp/logs` 在容器重启后会丢失。如需持久化: |
| 160 | + |
| 161 | +```yaml |
| 162 | +# docker-compose.yml |
| 163 | +services: |
| 164 | + mcp-server: |
| 165 | + volumes: |
| 166 | + - ./logs:/tmp/taptap-mcp/logs |
| 167 | +``` |
| 168 | +
|
| 169 | +或自定义日志路径到持久化目录: |
| 170 | +
|
| 171 | +```bash |
| 172 | +TAPTAP_MCP_LOG_ROOT=/data/logs |
| 173 | +``` |
| 174 | + |
| 175 | +## 实现细节 |
| 176 | + |
| 177 | +### LogWriter 类 |
| 178 | + |
| 179 | +`src/core/utils/logWriter.ts` - Proxy 和 Server 共用的日志写入器。 |
| 180 | + |
| 181 | +主要功能: |
| 182 | + |
| 183 | +- 同时输出到 stderr 和文件(Tee 模式) |
| 184 | +- 按日期自动轮转 |
| 185 | +- 自动清理过期日志 |
| 186 | +- 异步写入,不阻塞主流程 |
| 187 | + |
| 188 | +### 路径计算 |
| 189 | + |
| 190 | +```typescript |
| 191 | +// Proxy 路径 |
| 192 | +function getProxyLogDir(config: ProxyConfig, logRoot: string): string { |
| 193 | + const { user_id, project_id } = config.tenant; |
| 194 | + |
| 195 | + if (user_id && project_id) { |
| 196 | + return path.join(logRoot, 'proxy', user_id, project_id); |
| 197 | + } |
| 198 | + |
| 199 | + // 无 user_id/project_id 时使用 kid hash |
| 200 | + const kidHash = computeStableHash(config.auth.kid); |
| 201 | + return path.join(logRoot, 'proxy', kidHash); |
| 202 | +} |
| 203 | + |
| 204 | +// Server 路径 |
| 205 | +function getServerLogDir(logRoot: string, transport: string): string { |
| 206 | + if (transport === 'stdio') { |
| 207 | + const workspaceHash = computeStableHash(path.resolve(EnvConfig.workspaceRoot)); |
| 208 | + return path.join(logRoot, 'server', workspaceHash); |
| 209 | + } |
| 210 | + |
| 211 | + // SSE/HTTP 模式使用统一目录 |
| 212 | + return path.join(logRoot, 'server'); |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +## 使用示例 |
| 217 | + |
| 218 | +### 启用 Proxy 文件日志 |
| 219 | + |
| 220 | +```json |
| 221 | +{ |
| 222 | + "options": { |
| 223 | + "log": { |
| 224 | + "enabled": true |
| 225 | + } |
| 226 | + } |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +日志将写入: `/tmp/taptap-mcp/logs/proxy/{user_id}/{project_id}/proxy-2025-01-01.log` |
| 231 | + |
| 232 | +### 启用 Server 文件日志 |
| 233 | + |
| 234 | +```bash |
| 235 | +TAPTAP_MCP_LOG_FILE=true node dist/server.js |
| 236 | +``` |
| 237 | + |
| 238 | +日志将写入: `/tmp/taptap-mcp/logs/server/{workspace_hash}/server-2025-01-01.log` |
| 239 | + |
| 240 | +### 自定义日志路径 |
| 241 | + |
| 242 | +```bash |
| 243 | +# Server |
| 244 | +TAPTAP_MCP_LOG_ROOT=/var/log/myapp TAPTAP_MCP_LOG_FILE=true node dist/server.js |
| 245 | + |
| 246 | +# Proxy (config.json) |
| 247 | +{ |
| 248 | + "options": { |
| 249 | + "log": { |
| 250 | + "root": "/var/log/myapp", |
| 251 | + "enabled": true |
| 252 | + } |
| 253 | + } |
| 254 | +} |
| 255 | +``` |
0 commit comments