7.1 KiB
级别与上下文
关于日志级别语义、基于 context 的日志模式、性能考虑以及哪些内容不应出现在日志中的详细指导。
级别语义
Debug
仅开发人员的诊断。生产中默认禁用。用于跟踪在开发或故障排查期间有帮助的内部状态:
slog.Debug("cache lookup", "key", key, "hit", hit)
slog.Debug("parsed config", "fields", len(cfg.Fields))
slog.Debug("SQL query", "query", q, "args", args)
何时使用:内部状态转换、缓存行为、开发期间的详细请求/响应数据。
Info
确认系统按预期运行的重要事件。这些应在生产中对理解系统行为有用:
slog.Info("server started", "addr", addr, "version", version)
slog.Info("config loaded", "path", cfgPath, "env", env)
slog.Info("migration completed", "version", v, "elapsed_ms", elapsed)
slog.Info("user registered", "user_id", uid)
何时使用:启动/关闭、配置变更、重要业务事件、周期性健康摘要。
Warn
发生了意外的事情,但系统已恢复或优雅降级。运维人员可能想要调查但不需要立即行动:
slog.Warn("retry succeeded", "attempt", n, "endpoint", url)
slog.Warn("deprecated endpoint called", "path", r.URL.Path, "user_id", uid)
slog.Warn("rate limit approaching", "current", rate, "limit", max)
slog.Warn("fallback to default config", "err", err)
何时使用:最终成功的重试、弃用的代码路径、接近资源限制、回退行为。
Error
操作失败并需要运维人员关注。系统无法完成请求或任务:
slog.Error("payment failed", "err", err, "order_id", id, "amount", amt)
slog.Error("database connection lost", "err", err, "host", dbHost)
slog.Error("message processing failed", "err", err, "msg_id", msgID)
何时使用:影响用户的失败操作、丢失的连接、数据完整性问题、未恢复的外部服务故障。
始终包含错误:slog.Error 调用应始终带有包含实际错误值的 "err" 属性。
在 Warn 和 Error 之间选择
操作最终是否成功?
├─ 是(经过重试/回退后)→ Warn
└─ 否(调用者收到错误)→ Error
├─ 需要立即关注 → Error
└─ 可以等到下次审查 → Warn
自定义详细级别
slog 级别是整数。在标准级别之间定义自定义子级别以实现细粒度控制:
const (
LevelTrace = slog.Level(-8) // 低于 Debug
LevelNotice = slog.Level(2) // 在 Info 和 Warn 之间
)
slog.Log(ctx, LevelTrace, "detailed trace", "span_id", spanID)
使用 HandlerOptions.Level 配合 slog.LevelVar 在运行时控制最低级别。
基于 Context 的日志
模式 1:Context 中的日志器
在 context 中存储增强后的 *slog.Logger。每个中间件层添加自己的字段:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := authenticate(r)
logger := loggerFromCtx(r.Context()).With("user_id", userID)
ctx := context.WithValue(r.Context(), loggerKey, logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
优点:简单,与任何 handler 链配合使用。
缺点:需要纪律来始终使用 loggerFromCtx。
模式 2:显式日志器参数
将 *slog.Logger 作为函数参数与 context 一起传递:
func processOrder(ctx context.Context, logger *slog.Logger, order *Order) error {
logger.Info("processing order", "order_id", order.ID)
// ...
}
优点:显式依赖,更易测试,无需 context 键。 缺点:每个函数签名中都有额外参数。
何时使用哪种
| 场景 | 推荐 |
|---|---|
| HTTP 处理器 / 中间件链 | Context 中的日志器 |
| 无 HTTP 依赖的库代码 | 显式参数 |
| 后台工作器 / 批处理任务 | 显式参数 |
| 深层调用链(5 层以上) | Context 中的日志器 |
性能考虑
使用 Enabled() 预检查
当日志级别被禁用时避免分配日志参数:
// 开销大:参数始终被求值,即使 Debug 被禁用
slog.Debug("request details",
"headers", fmt.Sprintf("%v", r.Header),
"body", string(bodyBytes),
)
// 更好:禁用时完全跳过
if slog.Default().Enabled(ctx, slog.LevelDebug) {
slog.Debug("request details",
"headers", fmt.Sprintf("%v", r.Header),
"body", string(bodyBytes),
)
}
当参数构造开销大(格式化、序列化或读取数据)时,这很重要。对于简单属性(slog.String、slog.Int),开销可以忽略不计。
在热路径上使用 LogAttrs
slog.LogAttrs 避免了便捷方法(slog.Info 等)产生的 []any 分配:
// 标准——为键值对分配一个 []any
slog.Info("request handled", "method", r.Method, "status", code)
// 更快——类型化属性,无 []any 分配
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
slog.String("method", r.Method),
slog.Int("status", code),
)
避免在紧凑循环中记录日志
如果循环处理数千个项目,记录摘要而不是每次迭代:
// 不好:10k 项目批次中每个项目一条日志
for _, item := range items {
slog.Debug("processing item", "id", item.ID)
process(item)
}
// 好:记录摘要
slog.Info("batch started", "count", len(items))
processed, failed := processBatch(items)
slog.Info("batch completed", "processed", processed, "failed", failed)
不应记录的内容
密钥和凭证
永远不要记录:
- 密码、API 密钥、令牌(OAuth、JWT、会话)
- 私钥、证书
- 包含凭证的数据库连接字符串
// 不好
slog.Info("connecting", "dsn", dsn) // 可能包含密码
// 好
slog.Info("connecting", "host", dbHost, "database", dbName)
个人身份信息(PII)
除非调试所需且你的保留策略允许,否则避免记录:
- 电子邮件地址、电话号码
- 完整姓名、物理地址
- IP 地址(在某些司法管辖区)
- 信用卡号、社会安全号
如果必须记录用户标识符,使用不透明 ID 而非 PII。
高基数无界数据
不要记录完整的请求体、Info 级别的完整栈跟踪或无界集合:
// 不好:无界数据
slog.Info("received", "body", string(requestBody))
slog.Info("users loaded", "users", users) // 可能有 10 万条记录
// 好:有界摘要
slog.Info("received", "content_length", len(requestBody), "content_type", ct)
slog.Info("users loaded", "count", len(users))
决策表
| 数据类型 | 记录吗? | 替代方案 |
|---|---|---|
| 请求 ID / 跟踪 ID | 是 | — |
| 用户 ID(不透明的) | 是 | — |
| HTTP 方法、路径、状态 | 是 | — |
| 错误消息 | 是 | — |
| 密码 / 令牌 | 永不 | 记录令牌前缀或 "已脱敏" |
| 完整请求体 | 否 | 记录内容长度和类型 |
| PII(邮箱、姓名) | 避免 | 记录不透明用户 ID |
| 大型集合 | 否 | 记录数量或摘要 |
| 栈跟踪 | 仅 Debug | 使用 slog.Debug |