docs: 补充完善日志系统与链路追踪设计
- 架构文档新增第八节:日志系统与链路追踪设计 - Trace ID 全链路追踪(HTTP/WebSocket/跨服务) - 日志级别规范(DEBUG/INFO/WARN/ERROR/FATAL) - 结构化日志格式(开发文本/生产JSON) - 函数级日志规范(入口/出口模式) - WebSocket 日志追踪 - Go ↔ mediasoup Node 跨服务链路关联 - 敏感信息脱敏规则 - 前端错误上报机制 - 实施计划 Task 2 补充日志系统详细实现步骤 Made-with: Cursor
This commit is contained in:
@@ -245,3 +245,202 @@ services:
|
|||||||
| **uniapp (Vue 3)** | 一套代码多端运行(H5/App/小程序) |
|
| **uniapp (Vue 3)** | 一套代码多端运行(H5/App/小程序) |
|
||||||
| **Element Plus** | Vue 3 生态最成熟的 PC 端组件库 |
|
| **Element Plus** | Vue 3 生态最成熟的 PC 端组件库 |
|
||||||
| **Docker Compose** | 轻量级容器编排,适合初期和开发环境 |
|
| **Docker Compose** | 轻量级容器编排,适合初期和开发环境 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、日志系统与链路追踪设计
|
||||||
|
|
||||||
|
### 8.1 设计目标
|
||||||
|
|
||||||
|
- 每一个请求从进入系统到完成响应,全链路可追踪
|
||||||
|
- Go 服务内部的函数调用链清晰可见
|
||||||
|
- Go 服务与 mediasoup Node 服务之间的调用可关联
|
||||||
|
- WebSocket 消息的处理过程可追踪
|
||||||
|
- 日志分级明确,开发/生产环境日志策略不同
|
||||||
|
- 日志格式统一、结构化,便于后期接入 ELK/Loki 等日志平台
|
||||||
|
|
||||||
|
### 8.2 请求链路追踪(Trace ID / Request ID)
|
||||||
|
|
||||||
|
每个请求在进入系统时生成唯一的 `trace_id`,贯穿整个处理链路:
|
||||||
|
|
||||||
|
```
|
||||||
|
客户端请求
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Nginx(生成或转发 X-Request-ID)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Go 中间件(提取或生成 trace_id,注入 context)
|
||||||
|
│
|
||||||
|
├── Controller 日志:[trace_id] 收到请求 POST /api/v1/auth/login
|
||||||
|
├── Service 日志: [trace_id] 开始处理登录,account=zhangsan
|
||||||
|
├── DAO 日志: [trace_id] SQL查询 auth_users WHERE username=zhangsan
|
||||||
|
├── Redis 日志: [trace_id] SET echo:auth:token:1
|
||||||
|
│
|
||||||
|
├── 如果涉及 mediasoup 调用:
|
||||||
|
│ Go 将 trace_id 放入 HTTP Header 传给 Node 服务
|
||||||
|
│ Node 服务日志也携带同一个 trace_id
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Go 中间件:[trace_id] 请求完成 200 OK 耗时 25ms
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现方式:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 中间件:为每个请求生成 trace_id 并注入 context
|
||||||
|
func TraceMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
traceID := c.GetHeader("X-Request-ID")
|
||||||
|
if traceID == "" {
|
||||||
|
traceID = generateTraceID() // UUID 或 雪花算法
|
||||||
|
}
|
||||||
|
ctx := context.WithValue(c.Request.Context(), "trace_id", traceID)
|
||||||
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
c.Header("X-Request-ID", traceID)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 日志级别规范
|
||||||
|
|
||||||
|
| 级别 | 使用场景 | 示例 |
|
||||||
|
|------|---------|------|
|
||||||
|
| **DEBUG** | 开发调试信息,生产环境不输出 | 函数参数详情、SQL 语句、Redis 操作 |
|
||||||
|
| **INFO** | 正常业务流程的关键节点 | 用户登录成功、会议创建、消息发送 |
|
||||||
|
| **WARN** | 异常但不影响主流程 | 参数格式警告、缓存未命中、重试操作 |
|
||||||
|
| **ERROR** | 业务错误,需要关注 | 数据库查询失败、外部服务调用失败 |
|
||||||
|
| **FATAL** | 系统级致命错误 | 数据库连接失败、配置加载失败(启动时) |
|
||||||
|
|
||||||
|
**环境日志策略:**
|
||||||
|
|
||||||
|
| 环境 | 最低级别 | 输出方式 | 格式 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| 开发 | DEBUG | 控制台(彩色) | 可读文本 |
|
||||||
|
| 生产 | INFO | 文件 + 控制台 | JSON 结构化 |
|
||||||
|
|
||||||
|
### 8.4 日志格式规范
|
||||||
|
|
||||||
|
**结构化日志字段(每条日志必含):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"level": "info",
|
||||||
|
"timestamp": "2026-02-27T10:30:00.123Z",
|
||||||
|
"trace_id": "abc-123-def-456",
|
||||||
|
"module": "auth",
|
||||||
|
"func": "service.auth_service.Login",
|
||||||
|
"msg": "用户登录成功",
|
||||||
|
"user_id": 1,
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"latency_ms": 25
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 函数级日志规范
|
||||||
|
|
||||||
|
每个 Service 和 DAO 层的关键函数遵循统一的日志模式:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest) (*dto.LoginResponse, error) {
|
||||||
|
funcName := "service.auth_service.Login"
|
||||||
|
|
||||||
|
// 入口日志:记录关键入参(脱敏)
|
||||||
|
logs.Info(ctx, funcName, "开始处理登录",
|
||||||
|
zap.String("account", req.Account),
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
// 出口日志(失败):记录错误信息
|
||||||
|
logs.Error(ctx, funcName, "登录处理失败",
|
||||||
|
zap.String("account", req.Account),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 出口日志(成功)
|
||||||
|
logs.Info(ctx, funcName, "登录处理完成",
|
||||||
|
zap.String("account", req.Account),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 业务逻辑...
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.6 请求日志中间件
|
||||||
|
|
||||||
|
HTTP 请求日志中间件自动记录每个请求的完整信息:
|
||||||
|
|
||||||
|
```
|
||||||
|
[INFO] trace_id=abc-123 | POST /api/v1/auth/login | status=200 | latency=25ms | ip=192.168.1.100 | user_agent=Mozilla/5.0
|
||||||
|
[ERROR] trace_id=def-456 | POST /api/v1/meetings/join | status=500 | latency=150ms | ip=10.0.0.5 | error="mediasoup service unavailable"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.7 WebSocket 日志
|
||||||
|
|
||||||
|
WebSocket 连接和消息也纳入日志追踪体系:
|
||||||
|
|
||||||
|
```
|
||||||
|
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接建立
|
||||||
|
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 收到事件: im.message.send | conversation_id=5
|
||||||
|
[INFO] ws_conn=conn-789 | user_id=1 | trace_id=ws-001 | 消息处理完成 | msg_id=10086 | latency=8ms
|
||||||
|
[WARN] ws_conn=conn-789 | user_id=1 | 心跳超时,准备断开连接
|
||||||
|
[INFO] ws_conn=conn-789 | user_id=1 | WebSocket 连接断开 | 在线时长=3600s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.8 跨服务链路追踪(Go ↔ mediasoup Node)
|
||||||
|
|
||||||
|
Go 服务调用 mediasoup Node 服务时,通过 HTTP Header 传递 trace_id:
|
||||||
|
|
||||||
|
```
|
||||||
|
Go 服务:
|
||||||
|
[INFO] trace_id=abc-123 | 调用 mediasoup: POST /media/transport/create | room=123-456
|
||||||
|
|
||||||
|
mediasoup Node 服务:
|
||||||
|
[INFO] trace_id=abc-123 | 收到请求: POST /media/transport/create | room=123-456
|
||||||
|
[INFO] trace_id=abc-123 | Transport 创建成功 | transport_id=xxx | latency=5ms
|
||||||
|
|
||||||
|
Go 服务:
|
||||||
|
[INFO] trace_id=abc-123 | mediasoup 调用完成 | transport_id=xxx | latency=12ms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.9 敏感信息脱敏
|
||||||
|
|
||||||
|
日志中的敏感信息必须脱敏处理:
|
||||||
|
|
||||||
|
| 字段 | 脱敏规则 | 示例 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 密码 | 不记录 | `password=***` |
|
||||||
|
| Token | 只记录前后各 4 位 | `token=eyJh...5NiJ` |
|
||||||
|
| 邮箱 | 部分隐藏 | `email=zh***@example.com` |
|
||||||
|
| 手机号 | 中间 4 位隐藏 | `phone=138****8000` |
|
||||||
|
| IP | 完整记录(用于安全审计) | `ip=192.168.1.100` |
|
||||||
|
|
||||||
|
### 8.10 前端日志与错误上报
|
||||||
|
|
||||||
|
前端通过 API 上报关键错误和操作日志:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/client/log # 前端错误上报接口
|
||||||
|
|
||||||
|
{
|
||||||
|
"level": "error",
|
||||||
|
"module": "mediasoup-client",
|
||||||
|
"message": "Transport connection failed",
|
||||||
|
"stack": "Error: ICE connection failed...",
|
||||||
|
"page": "/meeting/room",
|
||||||
|
"user_agent": "...",
|
||||||
|
"timestamp": 1740700000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
前端需要记录的关键场景:
|
||||||
|
- WebSocket 连接失败/断线
|
||||||
|
- mediasoup-client Transport 连接失败
|
||||||
|
- API 请求超时/5xx 错误
|
||||||
|
- 页面 JS 异常(通过 `window.onerror` / `Vue.config.errorHandler`)
|
||||||
|
|||||||
@@ -109,10 +109,14 @@ git commit -m "infra: Docker Compose 开发环境(PostgreSQL + Redis)"
|
|||||||
- Create: `backend/go-service/pkg/db/postgres.go`
|
- Create: `backend/go-service/pkg/db/postgres.go`
|
||||||
- Create: `backend/go-service/pkg/db/redis.go`
|
- Create: `backend/go-service/pkg/db/redis.go`
|
||||||
- Create: `backend/go-service/pkg/logs/logger.go`
|
- Create: `backend/go-service/pkg/logs/logger.go`
|
||||||
|
- Create: `backend/go-service/pkg/logs/trace.go`
|
||||||
- Create: `backend/go-service/pkg/utils/response.go`
|
- Create: `backend/go-service/pkg/utils/response.go`
|
||||||
- Create: `backend/go-service/pkg/middleware/cors.go`
|
- Create: `backend/go-service/pkg/middleware/cors.go`
|
||||||
- Create: `backend/go-service/pkg/middleware/recovery.go`
|
- Create: `backend/go-service/pkg/middleware/recovery.go`
|
||||||
- Create: `backend/go-service/pkg/middleware/logger.go`
|
- Create: `backend/go-service/pkg/middleware/logger.go`
|
||||||
|
- Create: `backend/go-service/pkg/middleware/trace.go`
|
||||||
|
|
||||||
|
> **日志系统详细设计见** `docs/architecture/system-architecture.md` 第八节
|
||||||
|
|
||||||
**Step 1: 初始化 Go Module**
|
**Step 1: 初始化 Go Module**
|
||||||
|
|
||||||
@@ -121,7 +125,7 @@ Run: `cd backend/go-service && go mod init github.com/echochat/backend`
|
|||||||
**Step 2: 创建配置管理**
|
**Step 2: 创建配置管理**
|
||||||
|
|
||||||
创建 `config/config.go` 使用 viper 读取 YAML 配置 + 环境变量覆盖。
|
创建 `config/config.go` 使用 viper 读取 YAML 配置 + 环境变量覆盖。
|
||||||
创建 `config/config.dev.yaml` 包含开发环境默认配置(数据库连接、Redis 连接、JWT 密钥、服务端口等)。
|
创建 `config/config.dev.yaml` 包含开发环境默认配置(数据库连接、Redis 连接、JWT 密钥、服务端口、日志级别等)。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// config/config.go
|
// config/config.go
|
||||||
@@ -132,44 +136,80 @@ type Config struct {
|
|||||||
JWT JWTConfig `mapstructure:"jwt"`
|
JWT JWTConfig `mapstructure:"jwt"`
|
||||||
Log LogConfig `mapstructure:"log"`
|
Log LogConfig `mapstructure:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogConfig struct {
|
||||||
|
Level string `mapstructure:"level"` // debug/info/warn/error
|
||||||
|
Format string `mapstructure:"format"` // text(开发)/json(生产)
|
||||||
|
OutputPath string `mapstructure:"output_path"` // stdout 或文件路径
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 3: 创建日志系统**
|
**Step 3: 创建日志系统(核心)**
|
||||||
|
|
||||||
创建 `pkg/logs/logger.go` 使用 zap 结构化日志,包含 LogFunctionEntry 和 LogFunctionExit 工具函数。
|
创建 `pkg/logs/logger.go`:
|
||||||
|
- 基于 zap 的结构化日志封装
|
||||||
|
- 支持从 context 中提取 trace_id 自动附加到每条日志
|
||||||
|
- 提供分级日志方法:`Debug(ctx, func, msg, fields...)` / `Info(...)` / `Warn(...)` / `Error(...)`
|
||||||
|
- 敏感信息脱敏工具函数(邮箱、手机号、Token)
|
||||||
|
- 开发环境输出彩色可读文本,生产环境输出 JSON 结构化格式
|
||||||
|
|
||||||
|
创建 `pkg/logs/trace.go`:
|
||||||
|
- `GenerateTraceID()` — 生成唯一 trace ID(UUID v4 或雪花算法)
|
||||||
|
- `WithTraceID(ctx, traceID)` — 将 trace_id 注入 context
|
||||||
|
- `GetTraceID(ctx)` — 从 context 提取 trace_id
|
||||||
|
|
||||||
|
**日志输出格式示例(开发环境):**
|
||||||
|
```
|
||||||
|
2026-02-27 10:30:00.123 INFO [abc-123] auth | service.Login | 用户登录成功 | account=zhangsan | latency=25ms
|
||||||
|
```
|
||||||
|
|
||||||
|
**日志输出格式示例(生产环境 JSON):**
|
||||||
|
```json
|
||||||
|
{"level":"info","ts":"2026-02-27T10:30:00.123Z","trace_id":"abc-123","module":"auth","func":"service.Login","msg":"用户登录成功","account":"zhangsan","latency_ms":25}
|
||||||
|
```
|
||||||
|
|
||||||
**Step 4: 创建数据库连接**
|
**Step 4: 创建数据库连接**
|
||||||
|
|
||||||
创建 `pkg/db/postgres.go` — GORM + PostgreSQL 连接池。
|
创建 `pkg/db/postgres.go` — GORM + PostgreSQL 连接池。GORM 日志适配 zap,SQL 查询日志携带 trace_id。
|
||||||
创建 `pkg/db/redis.go` — go-redis 客户端。
|
创建 `pkg/db/redis.go` — go-redis 客户端。Redis 操作日志携带 trace_id。
|
||||||
|
|
||||||
**Step 5: 创建统一响应工具**
|
**Step 5: 创建统一响应工具**
|
||||||
|
|
||||||
创建 `pkg/utils/response.go` 包含 ResponseOK、ResponseBadRequest、ResponseUnauthorized、ResponseForbidden、ResponseError 等统一响应函数。
|
创建 `pkg/utils/response.go` 包含 ResponseOK、ResponseBadRequest、ResponseUnauthorized、ResponseForbidden、ResponseError 等统一响应函数。响应中包含 trace_id 便于前端反馈问题时定位。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
TraceID string `json:"trace_id,omitempty"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 6: 创建中间件**
|
**Step 6: 创建中间件**
|
||||||
|
|
||||||
|
创建 `pkg/middleware/trace.go` — **链路追踪中间件**:
|
||||||
|
- 从请求头提取 `X-Request-ID`,不存在则自动生成
|
||||||
|
- 注入 context,后续所有日志自动携带 trace_id
|
||||||
|
- 在响应头中返回 `X-Request-ID`
|
||||||
|
|
||||||
|
创建 `pkg/middleware/logger.go` — **请求日志中间件**:
|
||||||
|
- 记录每个请求的完整信息:方法、路径、状态码、耗时、IP、User-Agent
|
||||||
|
- 自动携带 trace_id
|
||||||
|
- 慢请求告警(>500ms 记录 WARN)
|
||||||
|
|
||||||
创建 `pkg/middleware/cors.go` — CORS 跨域中间件。
|
创建 `pkg/middleware/cors.go` — CORS 跨域中间件。
|
||||||
创建 `pkg/middleware/recovery.go` — Panic 恢复中间件。
|
创建 `pkg/middleware/recovery.go` — Panic 恢复中间件(捕获 panic 后记录 ERROR 日志含堆栈信息)。
|
||||||
创建 `pkg/middleware/logger.go` — 请求日志中间件。
|
|
||||||
|
|
||||||
**Step 7: 创建 main.go 入口**
|
**Step 7: 创建 main.go 入口**
|
||||||
|
|
||||||
创建 `cmd/server/main.go`:
|
创建 `cmd/server/main.go`:
|
||||||
1. 加载配置
|
1. 加载配置
|
||||||
2. 初始化日志
|
2. 初始化日志系统(根据配置设置级别和输出格式)
|
||||||
3. 连接数据库和 Redis
|
3. 连接数据库和 Redis
|
||||||
4. 创建 Gin Engine
|
4. 创建 Gin Engine
|
||||||
5. 注册中间件
|
5. 注册中间件(顺序:Trace → Logger → CORS → Recovery)
|
||||||
6. 注册路由(暂时只有健康检查 GET /health)
|
6. 注册路由(暂时只有健康检查 GET /health)
|
||||||
7. 启动 HTTP 服务
|
7. 启动 HTTP 服务
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user