推荐用 zap 写结构化操作日志,因其支持级别控制、JSON 输出、自动轮转;需分离操作与错误日志、过滤敏感信息、启用异步写入并监控成功率。

用 log 包写入操作日志最简单但有硬伤
Go 标准库的 log 包能快速把日志写到文件或控制台,比如:
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
log.SetOutput(file)
log.Printf("user %s logged in at %v", userID, time.Now())但它不支持日志级别(INFO/WARN/ERROR)、无结构化输出、无法自动轮转,线上服务一跑几天,app.log 就会涨到几个 GB,还得手动切分。别图省事直接上线用它打操作日志。
推荐用 zap 写结构化操作日志
zap 是 Uber 开源的高性能日志库,适合记录用户登录、订单创建、配置变更等关键操作。它默认输出 JSON,字段可检索,也支持带颜色的开发环境输出。
- 安装:
go get go.uber.org/zap - 基础用法(生产模式):
logger, _ := zap.NewProduction() defer logger.Sync() logger.Info("user login succeeded", zap.String("user_id", "u_123"), zap.String("ip", "192.168.1.100"), zap.String("action", "login")) - 注意:
zap.NewProduction()默认禁用 caller 信息,如需定位哪行代码打的日志,加zap.AddCaller() - 避免传错类型:用
zap.Int("count", count)而不是zap.String("count", strconv.Itoa(count)),否则字段类型混乱,查日志时过滤失效
操作日志要单独归档,别和错误日志混在一起
用户行为类日志(如“下单成功”“权限更新”)和系统错误(如“数据库连接超时”)语义不同,混在一个文件里会干扰问题排查。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
zap的zapcore.NewCore配两个输出目标:operation.log和error.log - 按 level 分流:只把
Info级别中带"category": "operation"字段的日志写进operation.log - 更稳妥的做法是业务层显式调用不同 logger 实例:
opLog.Info(...)vserrLog.Error(...),避免依赖字段匹配出错 - 别把敏感字段(密码、token)直接打进去——哪怕日志做了脱敏,也要在写入前就过滤掉
zap.String("token", token)这类调用
日志写入失败不能卡住主流程
操作日志本质是“尽力而为”,如果磁盘满、文件句柄耗尽或 zap 内部缓冲区爆了,logger.Info() 可能阻塞甚至 panic。实际部署中必须防御:
- 启用异步写入:
zap.NewAsync(logger.Core()),但要注意Sync()必须在进程退出前调用,否则最后一段日志丢失 - 设置缓冲区大小和丢弃策略:
zap.NewAtomicLevelAt(zap.InfoLevel)配合自定义Core,当队列满时 drop 低优先级日志(如 debug)而非阻塞 - 监控日志写入成功率:定期检查
logger.Core().CheckedEntry返回值,或用zap.WrapCore包一层统计 wrapper

















