hi-server/.trae/documents/APP_PC 报错日志收集接口与表设计.md
shanshanzhong 61cdc0ce23
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m17s
feat(log): 添加客户端错误日志采集功能
新增 log_message 表用于存储客户端错误日志,包含平台、设备信息、错误详情等字段
添加客户端上报接口和管理端查询接口
实现日志去重、限流和安全防护机制
2025-12-02 19:34:43 -08:00

151 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 背景与现状
- 技术栈Go + gin 路由internal/handler/routes.go、GORM + MySQLinternal/svc/serviceContext.go
- 现有日志:系统统一写入 `system_logs`internal/model/log/log.go并通过 `LogModel.FilterSystemLog` 提供查询internal/model/log/model.go。管理端已存在日志查询路由internal/handler/routes.go:188-236
- 新需求:新增专用表 `log_message`,用于 APP/PC/Web 客户端错误日志采集,避免与原有“消息发送/业务日志”混用(降低查询噪声、明确字段)。
## SQL 表设计MySQL
- 表名:`log_message`
- 目的:采集终端错误与异常信息,便于筛选、定位、汇总。
- 字段:
- `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
- `platform` VARCHAR(32) NOT NULL`android`/`ios`/`windows`/`mac`/`web`
- `app_version` VARCHAR(32) NULL
- `os_name` VARCHAR(32) NULL`Android``iOS``Windows``macOS`
- `os_version` VARCHAR(32) NULL
- `device_id` VARCHAR(64) NULL设备唯一标识便于去重/定位)
- `user_id` BIGINT NULL DEFAULT NULL关联已登录用户匿名为空
- `session_id` VARCHAR(64) NULL会话标识便于定位
- `level` TINYINT UNSIGNED NOT NULL DEFAULT 31=fatal 2=error 3=warn 4=info
- `error_code` VARCHAR(64) NULL业务/系统错误码)
- `message` TEXT NOT NULL错误简述
- `stack` MEDIUMTEXT NULL堆栈
- `context` JSON NULL扩展上下文如接口路径、参数、网络状态等
- `client_ip` VARCHAR(45) NULL由服务端按请求解析填充
- `user_agent` VARCHAR(255) NULLWeb/PC 端可用)
- `locale` VARCHAR(16) NULL如 zh-CN、en-US
- `digest` VARCHAR(64) NULL去重指纹message+stack+error_code+app_version+platform 的哈希)
- `occurred_at` DATETIME NULL客户端发生时间
- `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP服务端入库时间
- 索引:
- `idx_platform_time(platform, created_at)`
- `idx_user_time(user_id, created_at)`
- `idx_device_time(device_id, created_at)`
- `idx_error_code(error_code)`
- `uniq_digest(digest)`(可选唯一,避免大量重复日志)
- 迁移:
- 新增:`initialize/migrate/database/02105_log_message.up.sql`
- 回滚:`initialize/migrate/database/02105_log_message.down.sql`
- DDL 示例:
```
CREATE TABLE `log_message` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`platform` VARCHAR(32) NOT NULL,
`app_version` VARCHAR(32) NULL,
`os_name` VARCHAR(32) NULL,
`os_version` VARCHAR(32) NULL,
`device_id` VARCHAR(64) NULL,
`user_id` BIGINT NULL DEFAULT NULL,
`session_id` VARCHAR(64) NULL,
`level` TINYINT UNSIGNED NOT NULL DEFAULT 3,
`error_code` VARCHAR(64) NULL,
`message` TEXT NOT NULL,
`stack` MEDIUMTEXT NULL,
`context` JSON NULL,
`client_ip` VARCHAR(45) NULL,
`user_agent` VARCHAR(255) NULL,
`locale` VARCHAR(16) NULL,
`digest` VARCHAR(64) NULL,
`occurred_at` DATETIME NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_digest` (`digest`),
KEY `idx_platform_time` (`platform`, `created_at`),
KEY `idx_user_time` (`user_id`, `created_at`),
KEY `idx_device_time` (`device_id`, `created_at`),
KEY `idx_error_code` (`error_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
## 接口设计(采集)
- 路径:`POST /v1/common/log/message/report`
- 路由分组:`/v1/common`(复用 DeviceMiddleware见 internal/handler/routes.go:625-655
- 鉴权:可匿名;若已登录从上下文解析 `user_id` 注入。启用 IP/设备维度的限流Redis避免刷量。
- 请求体JSON
- `platform` string 必填
- `appVersion` string 可选
- `osName` string 可选
- `osVersion` string 可选
- `deviceId` string 可选
- `userId` number 可选(客户端不可信,以服务端解析为准)
- `sessionId` string 可选
- `level` number 可选(默认 3
- `errorCode` string 可选
- `message` string 必填
- `stack` string 可选
- `context` object 可选
- `occurredAt` number 可选(毫秒时间戳)
- 返回(统一封装):`{ "code": 0, "msg": "OK", "data": { "id": 123 } }`(参考 pkg/result/httpResult.go
- 处理逻辑:
- 从请求头解析 UA 与 IP 注入 `client_ip``user_agent`;校验字段长度与大小(如 `stack` 限制 1MB
- 计算 `digest`(如 `sha256(message|stack|errorCode|appVersion|platform)`),若唯一索引冲突则返回已存在的 ID 或静默忽略(防重复)。
- 使用 GORM 入库至 `log_message`
## 接口设计(管理端查询)
- 列表:`GET /v1/admin/log/message/error/list`
- 筛选:`platform``level``userId``deviceId``errorCode``keyword`(匹配 `message`/`stack`/`context`)、`start`/`end`(时间范围)
- 分页:`page``size`
- 详情:`GET /v1/admin/log/message/error/detail?id=...`
- 路由分组:`/v1/admin/log`(与现有日志保持一致,见 internal/handler/routes.go:188-236
- 返回:列表返回 `total``list`(含核心字段),详情返回全部字段。
## 数据模型与方法GORM 设计)
- 目录:`internal/model/logmessage/`
- 结构:
- `type LogMessage struct { ... }`(字段与上表对应,`TableName() string { return "log_message" }`
- 接口:
- `Insert(ctx context.Context, data *LogMessage) error`
- `Filter(ctx context.Context, params *FilterParams) ([]*LogMessage, int64, error)`(支持多维筛选、分页、关键字模糊)
- `FindOne(ctx context.Context, id int64) (*LogMessage, error)`
- 逻辑:仿照现有 `internal/model/log/default.go``model.go` 的模式(组合 `defaultModel` + `customModel`),保证代码一致性。
## 防护与合规
- 限流:按 `device_id``client_ip` 维度做分钟/小时限流(重用 Redisinternal/svc/serviceContext.go 已初始化)。
- 安全:
- 敏感信息剔除(避免在 `context`/`stack` 中泄露密钥/密码)
- 大字段截断与压缩策略(超限截断,或服务端配置开关)
- 隐私:遵循最小化原则,不采集不必要的 PII`user_id` 仅在登录上下文中由服务端注入。
## 客户端上报示例
- 示例请求:
```
POST /v1/common/log/message/report
{
"platform": "android",
"appVersion": "1.2.3",
"osName": "Android",
"osVersion": "14",
"deviceId": "a1b2c3",
"level": 2,
"errorCode": "NETWORK_TIMEOUT",
"message": "请求超时:/api/order/list",
"stack": "TimeoutException at...",
"context": { "api": "/api/order/list", "retry": 1 },
"occurredAt": 1733145600000
}
```
## 迁移与回滚
- 新增 `02105_log_message.up.sql`:创建表与索引。
- 回滚 `02105_log_message.down.sql``DROP TABLE IF EXISTS log_message;`
## 与现有日志体系的关系
- 管理端查询保持独立路由,避免与现有 `message/list`(邮件/短信发送日志混淆internal/handler/routes.go:207-213
- 可选加写 `system_logs` 一条摘要(`Type` 新增 `TypeClientError`),用于仪表盘总览;但核心数据以 `log_message` 为准。
## 后续实现要点
- 路由注册:`/v1/common/log/message/report``/v1/admin/log/message/error/*`
- 校验与限流中间件:复用现有 `DeviceMiddleware` 与 Redis。
- 单元测试:
- 入库成功/去重冲突/字段截断
- 多维筛选与分页
- 文档:在项目说明文档补充采集字段、接口规范与数据保留策略。