日志记录与请求 ID 中间件
结构化日志记录与注入跟踪 ID
日志记录与请求 ID 中间件 是 CoddyKit 上的免费 Go Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Go Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Go Academy 课程共包含 4 节课。
为什么需要结构化日志?
纯文本日志很难在大规模环境中解析。结构化日志(JSON 或 key=value)允许日志聚合器(Datadog、Loki、ELK)按字段筛选日志条目并建立关联。
生成请求 ID
在中间件中为每个请求生成唯一 ID(UUID 或 ULID),并将其附加到响应标头和上下文:
func RequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get("X-Request-ID")
if id == "" { id = uuid.New().String() }
w.Header().Set("X-Request-ID", id)
ctx := context.WithValue(r.Context(), reqIDKey{}, id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}使用 slog 的日志中间件
Go 1.21 新增了用于结构化日志记录的 log/slog。在中间件中使用它:
func StructuredLogger(logger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
cw := &captureWriter{ResponseWriter: w}
next.ServeHTTP(cw, r)
logger.Info("request",
"method", r.Method,
"path", r.URL.Path,
"status", cw.status,
"latency_ms", time.Since(start).Milliseconds(),
"request_id", r.Context().Value(reqIDKey{}),
)
})
}
}captureWriter
包装 ResponseWriter,以捕获处理程序写入的状态码:
type captureWriter struct {
http.ResponseWriter
status int
}
func (cw *captureWriter) WriteHeader(code int) {
cw.status = code
cw.ResponseWriter.WriteHeader(code)
}
func (cw *captureWriter) Status() int {
if cw.status == 0 { return http.StatusOK }
return cw.status
}将请求 ID 添加到每一条日志
为每个请求构建一个预先填充 ID 的子日志记录器,然后将其存储到上下文中:
reqLogger := logger.With("request_id", id)
ctx := context.WithValue(r.Context(), loggerKey{}, reqLogger)从上下文中获取日志记录器
提供辅助函数,以便从任意处理程序或服务中获取请求作用域内的日志记录器:
func LoggerFromContext(ctx context.Context) *slog.Logger {
l, ok := ctx.Value(loggerKey{}).(*slog.Logger)
if !ok { return slog.Default() }
return l
}日志级别
使用适当的级别:Debug 用于开发期间的详细日志,Info 用于请求摘要,Warn 用于可恢复的异常,Error 用于需要处理的失败。不要记录敏感字段(令牌、密码)。
延迟分桶
在每一条日志中包含请求延迟,并在指标系统(Prometheus)中聚合为直方图分桶,以跟踪 P50、P95 和 P99 百分位数。
微服务中的关联 ID
将传入请求中的 X-Request-ID 标头转发到所有传出的 HTTP 和 gRPC 调用,以便在不引入完整跟踪系统的情况下实现分布式跟踪。
日志采样
对于流量较高的服务,可以对调试级别和信息级别的日志进行采样(例如,每 100 个请求只记录 1 个调试级别日志),在保留完整错误日志的同时降低存储成本。
zap 与 slog 的比较
对于高吞吐量服务,zap(Uber)比 slog 更快,但需要进行更多配置。slog 属于标准库,具有良好的可移植性,足以满足大多数服务的需求。除非基准测试显示存在瓶颈,否则请优先使用 slog。
快速检查
为什么日志记录中间件需要 captureWriter?
回顾:日志记录与请求 ID
要点:
- 在中间件中生成或转发 X-Request-ID,并将其存储在上下文中
- 使用 slog 结合键值字段记录结构化日志
- captureWriter 拦截状态码,以便在处理程序执行后记录日志
- 将关联 ID 转发到所有下游调用
常见问题解答
「日志记录与请求 ID 中间件」课时是免费的吗?
是的 — 「日志记录与请求 ID 中间件」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Go Academy 课程的其余内容,请升级到 CoddyKit PRO。 Go Academy 课程共包含 4 节课。
「日志记录与请求 ID 中间件」这节课中我会学到什么?
结构化日志记录与注入跟踪 ID 你通过在浏览器中直接运行的动手代码来练习 Go Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Go Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Go Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「日志记录与请求 ID 中间件」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Go Academy 课中编写并运行代码吗?
能。每节 Go Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- HTTP 中间件链基础
- 日志记录与请求 ID 中间件
- 身份验证与速率限制中间件
- CORS 与 panic 恢复中间件