关于 go-logging
面向 Go 开发者的日志技能,介绍结构化日志、级别、上下文范围字段及敏感数据处理,适用于构建或审查生产日志,并建议新代码使用标准库 `log/slog`。
主要功能
- 01结构化键值日志
- 02选择日志级别
- 03上下文范围的日志字段
- 04排除敏感数据
- 05处理器配置与测试指导
适用场景
- 01为 Go 服务添加生产日志
- 02审查错误日志与返回路径
- 03避免在日志中记录密钥和个人数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-logging安装后在 Claude Code 中输入/go-logging确认已加载。
附带文件references/LEVELS-AND-CONTEXT.mdreferences/LOGGING-PATTERNS.md
详细介绍
Go 日志记录
兼容性:
log/slog需要 Go 1.21+;testing/slogtest需要 Go 1.22+。
资源路由
references/LEVELS-AND-CONTEXT.md- 选择日志级别、决定使用上下文中的日志记录器还是显式参数,或排除敏感字段时阅读。references/LOGGING-PATTERNS.md- 配置 slog 处理器、记录 HTTP 请求、测试处理器,或从log.Printf迁移时阅读。
核心原则
日志是给运维人员看的,不是给开发人员看的。每条日志都应帮助 他人诊断生产问题。如果日志不能起到这个作用,那它就是噪声。
选择日志记录器
规范性:新 Go 代码应使用
log/slog。
slog 是结构化的、分级的,并且位于标准库中(Go 1.21+)。它
足以满足绝大多数生产环境的日志记录需求。
Which logger?
├─ New production code → log/slog
├─ Trivial CLI / one-off → log (standard)
└─ Measured perf bottleneck → zerolog or zap (benchmark first)
除非性能分析表明 slog 是热路径上的瓶颈,否则不要引入第三方日志库。
如果确实引入,请保持相同的结构化键值风格。
结构化日志记录
规范性:始终使用键值对。不要将值插入消息字符串中。
消息是对所发生事件的静态描述。动态数据应放入 键值属性中:
// Good: static message, structured fields
slog.Info("order placed", "order_id", orderID, "total", total)
// Bad: dynamic data baked into the message string
slog.Info(fmt.Sprintf("order %d placed for $%.2f", orderID, total))
键命名
建议:日志属性键使用
snake_case。
键应使用小写字母、以下划线分隔,并在整个代码库中保持一致:user_id、request_id、elapsed_ms。
类型化属性
对于性能关键路径,使用类型化构造函数以避免分配:
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
slog.String("method", r.Method),
slog.Int("status", code),
slog.Duration("elapsed", elapsed),
)
日志级别
建议:始终遵循以下级别语义。
| 级别 | 使用场景 | 生产环境默认值 |
|---|---|---|
| Debug | 仅供开发人员使用的诊断信息、内部状态跟踪 | 禁用 |
| Info | 值得关注的生命周期事件:启动、关闭、配置已加载 | 启用 |
| Warn | 意外但可恢复的情况:使用了已弃用功能、重试成功 | 启用 |
| Error | 操作失败,需要运维人员关注 | 启用 |
经验法则:
- 如果不需要任何人采取行动,就不属于 Error——使用 Warn 或 Info
- 如果只有在附加调试器时才有用,就属于 Debug
slog.Error应始终包含"err"属性
slog.Error("payment failed", "err", err, "order_id", id)
slog.Warn("retry succeeded", "attempt", n, "endpoint", url)
slog.Info("server started", "addr", addr)
slog.Debug("cache lookup", "key", key, "hit", hit)
请求范围的日志记录
建议:从上下文派生日志记录器,以携带请求范围的字段。
使用中间件为日志记录器添加请求 ID、用户 ID 或跟踪 ID,然后 通过上下文或显式参数将增强后的日志记录器传递给下游。将完整的上下文键和 中间件实现保留在日志记录模式参考文档中,以便请求范围的日志记录由一个地方统一管理。
记录日志或返回错误,二选一,不要两者兼做
只处理一次的规则属于 go-error-handling。 在日志记录工作中,应通过选择本地记录日志并恢复的路径,或附带上下文返回错误的路径来应用此规则,同一错误不要两者兼做。
例外:HTTP 处理程序和其他调用栈顶层边界可以在服务端记录详细 错误,同时向客户端返回经过清理的消息:
if err != nil {
slog.Error("checkout failed", "err", err, "user_id", uid)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
有关完整的只处理一次模式和错误包装指南,请参阅 go-error-handling。
不应记录的内容
规范性:绝不要记录秘密、凭据、个人身份信息或基数高且无界的数据。
- 密码、API 密钥、令牌、会话 ID
- 完整信用卡号、SSN
- 可能包含用户数据的请求/响应正文
- 大小无界的整个切片或映射
快速参考
| 应做 | 不应做 |
|---|---|
slog.Info("msg", "key", val) | log.Printf("msg %v", val) |
| 静态消息 + 结构化字段 | 在消息中使用 fmt.Sprintf |
使用 snake_case 键 | 使用 camelCase 或不一致的键 |
| 记录日志或返回错误 | 记录日志并返回同一个错误 |
| 从上下文派生日志记录器 | 每次调用都创建新的日志记录器 |
使用带有 "err" 属性的 slog.Error | 对错误使用 slog.Info |
在热路径上预先检查 Enabled() | 始终分配日志参数 |
相关技能
- 错误处理:在决定记录还是返回错误,或使用一次性处理模式时,请参阅 go-error-handling
- 上下文传播:在通过上下文传递请求范围内的值(包括日志记录器)时,请参阅 go-context
- 性能:在优化热点路径日志记录或减少日志调用中的内存分配时,请参阅 go-performance
- 代码审查:在审查 Go PR 中的日志记录实践时,请参阅 go-code-review