gin.logger()不能当审计日志用,因为它只记录请求元信息,不读取body、不拦截response、无调用栈定位、不结构化、不落库强一致,且无法传递可信上下文。

为什么 gin.Logger() 不能当审计日志用
因为 gin.Logger() 只打印请求行、状态码、耗时、IP 等元信息,它不读取也不缓存 c.Request.Body,也不拦截 ResponseWriter。你 POST 一个 JSON,它不会记录 body;你返回一个用户列表,它不会记录 response 数据——这根本达不到审计日志“谁在什么时候干了啥、改了什么”的基本要求。
审计日志必须带 runtime.Caller(2) 捕获调用位置
原生 log.Printf 打出来的文件名和行号永远指向日志封装层,不是业务 handler 或 service 方法。审计日志要能定位到具体哪一行代码触发了删用户、改权限等操作,必须主动捕获上下文:
- 用
runtime.Caller(2)(跳过封装函数 + 当前包装层),别用 1 或 3,容易错位 - 把
file:line拼进日志 map,例如"caller": "user_handler.go:42" - 不要只打字符串,比如
log.Printf("[AUDIT] %s", msg)—— 这样字段无法结构化解析
高危操作必须同步落库,不能异步写文件或发消息
用户执行 DeleteUser、GrantAdmin、ExportData 这类操作时,审计日志是事后追责的唯一依据。如果只丢进 channel 异步处理,进程崩溃或队列积压会导致日志丢失:
- 先用
tx, err := db.Begin()开事务 - 写完审计记录(INSERT 到 audit_log 表)再执行业务逻辑
- 任一失败都
tx.Rollback(),保证审计与业务原子性一致 - audit_log 表至少含:
op_type(枚举字符串)、op_target(资源 ID)、op_data(JSON 字符串,存修改前/后值)、created_at(用time.Now().UTC()) -
op_data必须参数化插入,别用fmt.Sprintf拼 SQL —— JSON 里的双引号、单引号、转义字符会直接破坏语句
context.Context 是传审计元数据的唯一可靠通道
从 HTTP handler 到 service 再到 repository,跨多层传递 user_id、req_id、source_ip 时,靠函数参数硬塞会频繁改签名;用全局变量或 goroutine 局部变量又无法保证生命周期。正确做法是:
- 在入口中间件里从
c.Request提取RemoteAddr、Header["User-Agent"]、c.MustGet("user_id")等字段 - 统一注入
context.WithValue(c.Request.Context(), auditKey, auditMap) - 下游各层通过
c.Request.Context().Value(auditKey)安全取值,无需改函数签名 - 审计日志最终输出时,把整个
auditMap和业务字段一起json.Marshal写入io.Writer(如lumberjack.Logger)
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











