go-logging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Logging

Go 日志处理

Core Principle

核心原则

Logs are for operators, not developers. Every log line should help someone diagnose a production issue. If it doesn't serve that purpose, it's noise.

日志是给运维人员看的,而非开发人员。每一条日志都应能帮助排查生产环境问题。如果无法达到这个目的,那这条日志就是冗余信息。

Choosing a Logger

选择日志库

Normative: Use
log/slog
for new Go code.
slog
is structured, leveled, and in the standard library (Go 1.21+). It covers the vast majority of production logging needs.
Which logger?
├─ New production code      → log/slog
├─ Trivial CLI / one-off    → log (standard)
└─ Measured perf bottleneck → zerolog or zap (benchmark first)
Do not introduce a third-party logging library unless profiling shows
slog
is a bottleneck in your hot path. When you do, keep the same structured key-value style.
Read references/LOGGING-PATTERNS.md when setting up slog handlers, configuring JSON/text output, or migrating from log.Printf to slog.

规范要求:新的Go代码请使用
log/slog
slog
是结构化、分级的日志库,且属于标准库(Go 1.21+)。它能覆盖绝大多数生产环境日志需求。
如何选择日志库?
├─ 新的生产代码      → log/slog
├─ 简单CLI工具/一次性脚本 → log(标准库)
└─ 经测试的性能瓶颈场景 → zerolog或zap(先做基准测试)
除非性能分析显示
slog
是热路径中的瓶颈,否则不要引入第三方日志库。如果必须引入,请保持相同的结构化键值对风格。
当设置slog处理器、配置JSON/文本输出,或从log.Printf迁移到slog时,请阅读references/LOGGING-PATTERNS.md

Structured Logging

结构化日志

Normative: Always use key-value pairs. Never interpolate values into the message string.
The message is a static description of what happened. Dynamic data goes in key-value attributes:
go
// 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))
规范要求:始终使用键值对。切勿将动态值插入到消息字符串中。
消息部分是对事件的静态描述。动态数据应放在键值属性中:
go
// 推荐:静态消息+结构化字段
slog.Info("order placed", "order_id", orderID, "total", total)

// 不推荐:动态数据嵌入消息字符串
slog.Info(fmt.Sprintf("order %d placed for $%.2f", orderID, total))

Key Naming

键命名规范

Advisory: Use
snake_case
for log attribute keys.
Keys should be lowercase, underscore-separated, and consistent across the codebase:
user_id
,
request_id
,
elapsed_ms
.
建议:日志属性键使用
snake_case
格式。
键名应为小写、下划线分隔,且在整个代码库中保持一致:
user_id
request_id
elapsed_ms

Typed Attributes

类型化属性

For performance-critical paths, use typed constructors to avoid allocations:
go
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
    slog.String("method", r.Method),
    slog.Int("status", code),
    slog.Duration("elapsed", elapsed),
)
Read references/LEVELS-AND-CONTEXT.md when optimizing log performance or pre-checking with Enabled().

在性能敏感路径中,使用类型化构造函数避免内存分配:
go
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
    slog.String("method", r.Method),
    slog.Int("status", code),
    slog.Duration("elapsed", elapsed),
)
当优化日志性能或使用Enabled()预检查时,请阅读references/LEVELS-AND-CONTEXT.md

Log Levels

日志级别

Advisory: Follow these level semantics consistently.
LevelWhen to useProduction default
DebugDeveloper-only diagnostics, tracing internal stateDisabled
InfoNotable lifecycle events: startup, shutdown, config loadedEnabled
WarnUnexpected but recoverable: deprecated feature used, retry succeededEnabled
ErrorOperation failed, requires operator attentionEnabled
Rules of thumb:
  • If nobody should act on it, it's not Error — use Warn or Info
  • If it's only useful with a debugger attached, it's Debug
  • slog.Error
    should always include an
    "err"
    attribute
go
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)
Read references/LEVELS-AND-CONTEXT.md when choosing between Warn and Error or defining custom verbosity levels.

建议:始终遵循以下级别语义。
级别使用场景生产环境默认设置
Debug仅用于开发人员诊断、追踪内部状态禁用
Info重要的生命周期事件:启动、关闭、配置加载完成启用
Warn意外但可恢复的情况:使用了已废弃特性、重试成功启用
Error操作失败,需要运维人员关注启用
经验法则
  • 如果不需要任何人采取行动,那它就不是Error级别——使用Warn或Info级别
  • 只有在连接调试器时才有用的内容,使用Debug级别
  • slog.Error
    必须始终包含
    "err"
    属性
go
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)
当在Warn和Error级别之间做选择,或定义自定义详细级别时,请阅读references/LEVELS-AND-CONTEXT.md

Request-Scoped Logging

请求范围日志

Advisory: Derive loggers from context to carry request-scoped fields.
Use middleware to enrich a logger with request ID, user ID, or trace ID, then pass the enriched logger downstream via context or as an explicit parameter:
go
func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        logger := slog.With("request_id", requestID(r))
        ctx := context.WithValue(r.Context(), loggerKey, logger)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
All subsequent log calls in that request carry
request_id
automatically.
Read references/LOGGING-PATTERNS.md when implementing logging middleware or passing loggers through context.

建议:从上下文派生日志实例,以携带请求范围的字段。
使用中间件为日志实例添加请求ID、用户ID或追踪ID,然后通过上下文或显式参数将增强后的日志实例传递到下游:
go
func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        logger := slog.With("request_id", requestID(r))
        ctx := context.WithValue(r.Context(), loggerKey, logger)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
该请求后续的所有日志调用都会自动携带
request_id
字段。
当实现日志中间件或通过上下文传递日志实例时,请阅读references/LOGGING-PATTERNS.md

Log or Return, Not Both

日志或返回错误,不要同时做

Normative: Handle each error exactly once — either log it or return it.
Logging an error and then returning it causes duplicate noise as callers up the stack also handle the error.
go
// Bad: logged here AND by every caller up the stack
if err != nil {
    slog.Error("query failed", "err", err)
    return fmt.Errorf("query: %w", err)
}

// Good: wrap and return — let the caller decide
if err != nil {
    return fmt.Errorf("query: %w", err)
}
Exception: HTTP handlers and other top-of-stack boundaries may log detailed errors server-side while returning a sanitized message to the client:
go
if err != nil {
    slog.Error("checkout failed", "err", err, "user_id", uid)
    http.Error(w, "internal error", http.StatusInternalServerError)
    return
}
See go-error-handling for the full handle-once pattern and error wrapping guidance.

规范要求:每个错误只处理一次——要么记录日志,要么返回错误。
记录错误后又返回它会导致冗余日志,因为调用栈上游的代码也会处理该错误。
go
// 不推荐:此处记录日志,且调用栈上游的所有调用者也会记录
if err != nil {
    slog.Error("query failed", "err", err)
    return fmt.Errorf("query: %w", err)
}

// 推荐:包装后返回——让调用者决定如何处理
if err != nil {
    return fmt.Errorf("query: %w", err)
}
例外情况:HTTP处理器和其他栈顶边界组件可以在服务器端记录详细错误,同时向客户端返回经过脱敏的消息:
go
if err != nil {
    slog.Error("checkout failed", "err", err, "user_id", uid)
    http.Error(w, "internal error", http.StatusInternalServerError)
    return
}
完整的一次性处理模式和错误包装指导,请参考go-error-handling

What NOT to Log

禁止记录的内容

Normative: Never log secrets, credentials, PII, or high-cardinality unbounded data.
  • Passwords, API keys, tokens, session IDs
  • Full credit card numbers, SSNs
  • Request/response bodies that may contain user data
  • Entire slices or maps of unbounded size
Read references/LEVELS-AND-CONTEXT.md when deciding what data is safe to include in log attributes.

规范要求:切勿记录密钥、凭证、个人身份信息(PII)或高基数无界数据。
  • 密码、API密钥、令牌、会话ID
  • 完整信用卡号、社保号码
  • 可能包含用户数据的请求/响应体
  • 无界大小的完整切片或映射
当判断哪些数据可以安全地包含在日志属性中时,请阅读references/LEVELS-AND-CONTEXT.md

Quick Reference

快速参考

DoDon't
slog.Info("msg", "key", val)
log.Printf("msg %v", val)
Static message + structured fields
fmt.Sprintf
in message
snake_case
keys
camelCase or inconsistent keys
Log OR return errorsLog AND return the same error
Derive logger from contextCreate a new logger per call
Use
slog.Error
with
"err"
attr
slog.Info
for errors
Pre-check
Enabled()
on hot paths
Always allocate log args

推荐做法不推荐做法
slog.Info("msg", "key", val)
log.Printf("msg %v", val)
静态消息 + 结构化字段在消息中使用
fmt.Sprintf
snake_case
格式的键名
camelCase或不一致的键名
日志记录 或 返回错误同时记录日志并返回同一个错误
从上下文派生日志实例每次调用都创建新的日志实例
slog.Error
搭配
"err"
属性
使用
slog.Info
记录错误
在热路径中预检查
Enabled()
始终分配日志参数

Related Skills

相关技能

  • Error handling: See go-error-handling when deciding whether to log or return an error, or for the handle-once pattern
  • Context propagation: See go-context when passing request-scoped values (including loggers) through context
  • Performance: See go-performance when optimizing hot-path logging or reducing allocations in log calls
  • Code review: See go-code-review when reviewing logging practices in Go PRs
  • 错误处理:当决定是记录日志还是返回错误,或了解一次性处理模式时,请参考go-error-handling
  • 上下文传递:当通过上下文传递请求范围的值(包括日志实例)时,请参考go-context
  • 性能优化:当优化热路径日志或减少日志调用中的内存分配时,请参考go-performance
  • 代码评审:当评审Go语言PR中的日志实践时,请参考go-code-review

Reference Files

参考文件

  • references/LOGGING-PATTERNS.md — slog setup, handler configuration, HTTP middleware, migration from log
  • references/LEVELS-AND-CONTEXT.md — Level semantics, context-based logging, performance, what not to log
  • references/LOGGING-PATTERNS.md — slog设置、处理器配置、HTTP中间件、从log迁移的相关内容
  • references/LEVELS-AND-CONTEXT.md — 级别语义、基于上下文的日志、性能优化、禁止记录内容的相关内容