Skip to content

Commit 35db772

Browse files
committed
feat(log): add file logging support for Server and Proxy
- Add LogWriter class for Tee mode output (stderr + file) - Add unified LogLevel type (RFC 5424 standard) - Add log configuration via environment variables (Server) and JSON config (Proxy) - Support log rotation by date and automatic cleanup - Add LOG_SYSTEM.md documentation New environment variables: - TAPTAP_MCP_LOG_ROOT: log root directory - TAPTAP_MCP_LOG_FILE: enable file logging - TAPTAP_MCP_LOG_LEVEL: log level (RFC 5424) - TAPTAP_MCP_LOG_MAX_DAYS: log retention days
1 parent 619dda3 commit 35db772

12 files changed

Lines changed: 974 additions & 117 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -343,17 +343,22 @@ npm run format
343343

344344
### 环境变量(常用)
345345

346-
| 变量名 | 说明 | 默认值 |
347-
| ---------------------- | -------------------------- | --------------------- |
348-
| `TAPTAP_MCP_TRANSPORT` | 传输协议(stdio/sse/http) | stdio |
349-
| `TAPTAP_MCP_PORT` | HTTP/SSE 模式端口 | 3000 |
350-
| `TAPTAP_MCP_VERBOSE` | 详细日志模式 | false |
351-
| `TAPTAP_MCP_ENV` | 环境选择(production/rnd) | production |
352-
| `TAPTAP_MCP_CACHE_DIR` | 缓存根目录 | /tmp/taptap-mcp/cache |
353-
| `TAPTAP_MCP_TEMP_DIR` | 临时文件根目录 | /tmp/taptap-mcp/temp |
354-
| `WORKSPACE_ROOT` | 工作空间根路径(推荐设置) | process.cwd() |
346+
| 变量名 | 说明 | 默认值 |
347+
| ------------------------- | -------------------------- | --------------------- |
348+
| `TAPTAP_MCP_TRANSPORT` | 传输协议(stdio/sse/http) | stdio |
349+
| `TAPTAP_MCP_PORT` | HTTP/SSE 模式端口 | 3000 |
350+
| `TAPTAP_MCP_VERBOSE` | 详细日志模式 | false |
351+
| `TAPTAP_MCP_ENV` | 环境选择(production/rnd) | production |
352+
| `TAPTAP_MCP_CACHE_DIR` | 缓存根目录 | /tmp/taptap-mcp/cache |
353+
| `TAPTAP_MCP_TEMP_DIR` | 临时文件根目录 | /tmp/taptap-mcp/temp |
354+
| `WORKSPACE_ROOT` | 工作空间根路径(推荐设置) | process.cwd() |
355+
| `TAPTAP_MCP_LOG_ROOT` | 日志根目录 | /tmp/taptap-mcp/logs |
356+
| `TAPTAP_MCP_LOG_FILE` | 是否启用文件日志 | false |
357+
| `TAPTAP_MCP_LOG_LEVEL` | 文件日志级别 | info |
358+
| `TAPTAP_MCP_LOG_MAX_DAYS` | 日志保留天数 | 7 |
355359

356360
**完整环境变量说明:** [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)
361+
**日志系统说明:** [docs/LOG_SYSTEM.md](docs/LOG_SYSTEM.md)
357362

358363
## 开发规范
359364

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ npm test
236236
- `TAPTAP_MCP_CACHE_DIR` - 缓存目录(默认 `/tmp/taptap-mcp/cache`
237237
- `TAPTAP_MCP_TEMP_DIR` - 临时文件目录(默认 `/tmp/taptap-mcp/temp`
238238

239+
**日志配置**:
240+
241+
- `TAPTAP_MCP_LOG_ROOT` - 日志根目录(默认 `/tmp/taptap-mcp/logs`
242+
- `TAPTAP_MCP_LOG_FILE` - 启用文件日志:`true``false`(默认 `false`
243+
- `TAPTAP_MCP_LOG_LEVEL` - 日志级别(RFC 5424):`debug``info``notice``warning``error``critical``alert``emergency`(默认 `info`
244+
- `TAPTAP_MCP_LOG_MAX_DAYS` - 日志保留天数(默认 7)
245+
246+
详细说明请参考 [docs/LOG_SYSTEM.md](docs/LOG_SYSTEM.md)
247+
239248
### 添加新功能
240249

241250
```bash

docs/LOG_SYSTEM.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
```

src/core/types/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export interface MacToken {
2828
export type { RequestContext, AppContext, SessionContext, TokenSource } from './context.js';
2929
export { getTokenSourceLabel } from './context.js';
3030

31+
// ============================================================================
32+
// Log Types - 日志相关类型(无外部依赖,Server 和 Proxy 共用)
33+
// ============================================================================
34+
export type { LogLevel, LogConfig } from './log.js';
35+
export { LOG_LEVEL_PRIORITY } from './log.js';
36+
3137
// 前向声明
3238
import type { ResolvedContext as RC } from './context.js';
3339

src/core/types/log.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* 日志相关类型定义
3+
*
4+
* 这是一个纯类型文件,不依赖任何外部包。
5+
* Server 和 Proxy 共用这些类型。
6+
*/
7+
8+
/**
9+
* RFC 5424 日志级别(syslog severity levels)
10+
*
11+
* 数字越小优先级越高(更严重)
12+
*
13+
* @see https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1
14+
*/
15+
export type LogLevel =
16+
| 'emergency' // 0 - 系统不可用
17+
| 'alert' // 1 - 必须立即采取行动
18+
| 'critical' // 2 - 临界条件
19+
| 'error' // 3 - 错误条件
20+
| 'warning' // 4 - 警告条件
21+
| 'notice' // 5 - 正常但重要的条件
22+
| 'info' // 6 - 信息性消息
23+
| 'debug'; // 7 - 调试级别消息
24+
25+
/**
26+
* 日志级别优先级(RFC 5424 标准,数字越小优先级越高)
27+
*/
28+
export const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
29+
emergency: 0,
30+
alert: 1,
31+
critical: 2,
32+
error: 3,
33+
warning: 4,
34+
notice: 5,
35+
info: 6,
36+
debug: 7,
37+
};
38+
39+
/**
40+
* 日志配置接口
41+
*/
42+
export interface LogConfig {
43+
/** 日志根目录 */
44+
root?: string;
45+
/** 是否启用文件日志 */
46+
enabled?: boolean;
47+
/** 日志级别 */
48+
level?: LogLevel;
49+
/** 日志保留天数 */
50+
maxDays?: number;
51+
}

0 commit comments

Comments
 (0)