All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m17s
新增 log_message 表用于存储客户端错误日志,包含平台、设备信息、错误详情等字段 添加客户端上报接口和管理端查询接口 实现日志去重、限流和安全防护机制
7.4 KiB
7.4 KiB
背景与现状
- 技术栈:Go + gin 路由(internal/handler/routes.go)、GORM + MySQL(internal/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 - 目的:采集终端错误与异常信息,便于筛选、定位、汇总。
- 字段:
idBIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEYplatformVARCHAR(32) NOT NULL(如android/ios/windows/mac/web)app_versionVARCHAR(32) NULLos_nameVARCHAR(32) NULL(如Android、iOS、Windows、macOS)os_versionVARCHAR(32) NULLdevice_idVARCHAR(64) NULL(设备唯一标识,便于去重/定位)user_idBIGINT NULL DEFAULT NULL(关联已登录用户;匿名为空)session_idVARCHAR(64) NULL(会话标识,便于定位)levelTINYINT UNSIGNED NOT NULL DEFAULT 3(1=fatal 2=error 3=warn 4=info)error_codeVARCHAR(64) NULL(业务/系统错误码)messageTEXT NOT NULL(错误简述)stackMEDIUMTEXT NULL(堆栈)contextJSON NULL(扩展上下文,如接口路径、参数、网络状态等)client_ipVARCHAR(45) NULL(由服务端按请求解析填充)user_agentVARCHAR(255) NULL(Web/PC 端可用)localeVARCHAR(16) NULL(如 zh-CN、en-US)digestVARCHAR(64) NULL(去重指纹:message+stack+error_code+app_version+platform 的哈希)occurred_atDATETIME NULL(客户端发生时间)created_atDATETIME 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):
platformstring 必填appVersionstring 可选osNamestring 可选osVersionstring 可选deviceIdstring 可选userIdnumber 可选(客户端不可信,以服务端解析为准)sessionIdstring 可选levelnumber 可选(默认 3)errorCodestring 可选messagestring 必填stackstring 可选contextobject 可选occurredAtnumber 可选(毫秒时间戳)
- 返回(统一封装):
{ "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。
- 从请求头解析 UA 与 IP 注入
接口设计(管理端查询)
- 列表:
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) errorFilter(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维度做分钟/小时限流(重用 Redis,internal/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。 - 单元测试:
- 入库成功/去重冲突/字段截断
- 多维筛选与分页
- 文档:在项目说明文档补充采集字段、接口规范与数据保留策略。