审计日志必须带调用位置,否则无法定位业务代码真实位置;需用runtime.caller(2)捕获文件行号,context透传元数据,高危操作同步落库,字段统一结构化并物理隔离。

审计日志必须带调用位置,否则查不到谁干的
Go 原生 log.Printf 打出来的行号永远指向封装层,不是业务代码真实位置。审计日志要能定位到具体 handler 或 service 方法,必须主动捕获调用栈。
- 用
runtime.Caller(2)获取调用方文件和行号(跳过日志封装函数 + 当前包装层),别用1,容易错位 - 把
file:line拼进结构化字段,比如"caller": "user_handler.go:42" - 避免在中间件里直接
log.Println—— 审计日志不是调试日志,不能只靠log.Lshortfile伪装上下文
所有关键元数据必须走 context.Context 透传
HTTP handler → service → repo 多层调用时,硬塞参数或依赖全局变量都会导致字段丢失或竞态。唯一可靠通道是 context.Context。
- 在入口 middleware 注入
ctx = context.WithValue(ctx, audit.UserIDKey, userID)、audit.IPKey、audit.ReqIDKey等键值 - 日志写入前校验这些字段是否全非空;缺一则打
warning并补默认值(如"system"),防止日志残缺 - 别把
user_id从 session 或 token 里临时取一次就丢——异步 goroutine 里context一丢,userID就成空字符串
高危操作必须同步落库,不能异步或只写文件
删库、改权限、导出敏感数据这类操作,如果只丢进 channel 异步处理,进程崩溃或队列积压会导致日志丢失。审计日志是事后追责依据,必须强持久化。
- 对高危操作(如
DeleteUser、GrantAdmin),先用tx, err := db.Begin()开事务 - 先写审计记录(
INSERT INTO audit_log (...) VALUES (?, ?, ?)),再执行业务逻辑,任一失败都tx.Rollback() - 审计表字段至少含:
op_type(枚举字符串)、op_target(资源 ID)、op_data(JSON 字符串,含修改前/后值)、created_at(用time.Now().UTC()) - 别用
fmt.Sprintf拼 SQL ——op_data里可能含单引号或 JSON 转义字符,必须参数化
审计日志必须物理隔离,不能和应用日志混用
混在一起会破坏审计合规性:运维无法单独归档,安全团队 grep 效率低,等保要求的保留策略(如 180 天)也无法落地。
- 用独立的
*log.Logger实例,输出到专用路径(如/var/log/app/audit.log) - 配
lumberjack.Logger时设MaxAge: 180,禁止写入stdout或通用 JSON 日志流 - 写入前做最小化脱敏:手机号掩码为
138****1234,但额外存sha256(idCard)供紧急溯源;SQL 语句剥离参数值,只留UPDATE users SET status = ? WHERE id = ?
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











