应统一在中间件开头预读请求体并重置body:get用c.request.url.string();json请求先c.getrawdata()再重置body;表单请求调用c.parsemultipartform(32

中间件里怎么拿到请求完整路径和参数
Gin 的 gin.Context 本身不自动解析 query 和 form 参数,直接读 c.Request.URL.RawQuery 或 c.PostForm 容易漏掉 JSON body 或 multipart 数据。审计日志要“完整”,就得统一收口。
推荐在中间件开头调用 c.ShouldBind(或显式判断 c.Request.Method + c.GetRawData())来预读 body,再用 c.Request.URL.String() 拼出完整 URL(含 query)。注意:c.Request.Body 只能读一次,预读后需用 io.NopCloser 重置,否则后续 handler 会收不到数据。
- GET 请求:用
c.Request.URL.String()即可,query 已包含 - POST/PUT 且 Content-Type 是
application/json:先c.GetRawData(),再bytes.NewReader()赋给c.Request.Body - 表单类请求(
application/x-www-form-urlencoded或multipart/form-data):调用c.ParseMultipartForm(32 后,<code>c.Request.PostForm和c.MultipartForm才可用
如何生成唯一 trace_id 并贯穿整个请求链路
单纯用 uuid.NewString() 没问题,但必须在中间件最开始就生成,并通过 c.Set("trace_id", id) 存入上下文,后续所有日志、DB 查询、RPC 调用都从 c.GetString("trace_id") 取值。别依赖全局变量或 time.Now().UnixNano() —— 并发下不唯一,也难关联。
如果上游已带 X-Trace-ID 或 traceparent(W3C Trace Context),优先复用它,否则才生成新 ID。Gin 默认不解析 header,得手动取:
traceID := c.GetHeader("X-Trace-ID")
if traceID == "" {
traceID = uuid.NewString()
}
c.Set("trace_id", traceID)
注意:别把 trace_id 写进 response header 就完事,审计日志里没它等于没做追踪。
审计日志该记录哪些字段才算“完整”
不是越全越好,而是要满足事后回溯定位问题的基本诉求。必记字段包括:trace_id、method、path、status_code、latency、client_ip、user_id(如果有认证)、req_size、resp_size。其中 user_id 不能硬编码,应从认证中间件(如 JWT 解析结果)中获取并存到 c 上。
敏感字段如密码、token、身份证号等,必须脱敏——不是简单 replace,而是识别字段名后整体掩码(例如 "id_card": "110***1234")。别在日志里拼接原始 body 字符串,容易泄露。
-
c.ClientIP()可能返回反向代理 IP,真客户端 IP 应从X-Real-IP或X-Forwarded-For取(需信任代理) -
c.Writer.Size()在写响应前为 0,得用自定义ResponseWriter包装器才能准确获取resp_size - 避免在日志里打
panic堆栈——那是错误日志的事,审计日志只记录“发生了什么”,不记录“为什么崩了”
为什么 audit 日志不能直接用 log.Printf 或 zap.Info
audit 日志本质是结构化行为流水,不是 debug 信息。用 log.Printf 输出纯文本,后期根本没法按 path=/api/user 或 status_code=401 过滤;用 zap.Info 但没设 zap.String("event", "audit") 字段,就和其他业务日志混在一起,查起来像大海捞针。
正确做法是:单独初始化一个 *zap.Logger 实例,配置专用的 EncoderConfig,强制输出 event、level、trace_id 等固定字段,并写入独立文件或转发到日志系统(如 Loki、ELK)。同时禁用该 logger 的 development 模式(避免加堆栈),因为 audit 不需要。
更关键的是:审计日志必须异步写入,否则慢盘 I/O 会拖垮接口延迟。Zap 自带 zap.AddSync + lumberjack 轮转虽稳,但高并发下仍建议走消息队列(如 Kafka)或内存缓冲 + 定时刷盘。
真正麻烦的不是写几行日志,而是字段对齐、脱敏规则统一、trace_id 全链路透传、以及和 DB 操作日志、第三方调用日志的时间戳对齐——这些细节没压住,审计就只是看起来很全。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











