Go slog 怎样补充请求上下文字段
Go 的 log/slog 里,InfoContext 会把 context.Context 交给 Handler,但不会自动把 context 中的任意值变成日志字段。要让每条请求日志稳定带上 request_id、HTTP 方法和路径,最稳妥的做法是在请求入口提取允许记录的字段,用 Logger.With 创建请求级 logger,再把它沿调用链传给业务代码。
记住两层职责:context 负责携带请求生命周期和值,slog Attr 负责形成结构化日志字段。只调用 InfoContext 并不能替代把字段显式加入 logger 或 Handler。
- 已有请求上下文时优先使用
InfoContext、LogAttrs,让 Handler 能拿到 context。 - 稳定、低敏的请求字段在入口用
logger.With固定下来,业务层只接收请求级 logger。 - 只有需要统一策略时才包装 Handler 读取 context,并明确字段白名单、覆盖顺序和并发边界。
先分清 context 与日志属性
slog.Logger.Log、LogAttrs 以及顶层的 InfoContext 等方法都接收 context。标准 Handler 可以从这个参数读取 trace ID 一类的值,但它不会猜测你的业务键,更不会把 context.WithValue 中的所有内容遍历成日志。这样设计可以避免把取消信号、私密对象或不适合序列化的数据意外写入日志。

请求入口先创建请求级 logger
把字段加入 logger 的位置应尽量靠近请求入口。下面的中间件只提取可公开记录的字段:请求 ID 来自受控的请求头或服务端生成值,方法和路径来自请求本身;不要把 Cookie、Authorization 或完整查询参数直接塞进日志。
package main
import (
"context"
"log/slog"
"net/http"
"os"
)
type loggerKey struct{}
func requestLogger(base *slog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = "generated-by-service"
}
// 只把允许落盘的低敏字段放进请求级 logger,避免记录认证信息。
reqLogger := base.With(
slog.String("request_id", requestID),
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
)
ctx := context.WithValue(r.Context(), loggerKey{}, reqLogger)
// 把新的 context 绑定到请求,后续处理器可取出同一个 logger。
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func loggerFrom(ctx context.Context, fallback *slog.Logger) *slog.Logger {
// 缺少请求 logger 时使用 fallback,避免后台任务因类型断言失败而崩溃。
if logger, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok {
return logger
}
return fallback
}
func main() {
base := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{}))
_ = requestLogger
_ = loggerFrom
_ = base
}
这里的 context 只保存请求级 logger,而不是把一组可变字段放进全局变量。每个请求得到一个新的 logger,Logger.With 返回新 logger,不会改变共享的基础 logger,因此并发请求之间不会互相串字段。
业务层用 InfoContext 保留请求语义
在处理器或服务方法中取出请求级 logger后,使用带 context 的方法写日志。这样 Handler 仍能获得取消状态、链路追踪等上下文;稳定字段由 logger 自己携带,临时字段用 slog.Attr 加到当前记录。
func handleOrder(w http.ResponseWriter, r *http.Request, base *slog.Logger) {
logger := loggerFrom(r.Context(), base)
// LogAttrs 只接收 Attr,适合固定字段类型并减少参数歧义。
logger.LogAttrs(r.Context(), slog.LevelInfo, "开始处理订单",
slog.String("order_id", "order-1001"),
)
// 记录结束状态时继续使用同一个请求级 logger,字段会自动跟随。
logger.InfoContext(r.Context(), "订单处理完成",
slog.Int("status", http.StatusOK),
)
w.WriteHeader(http.StatusOK)
}
使用 JSONHandler 时,结果会是结构化对象,例如包含 request_id、method、path 和 order_id 等键。换成 TextHandler 只改变序列化外观,不改变字段来源。对高频日志,LogAttrs 比交替传入键和值更容易保持类型清晰;不需要为了补字段而拼接字符串。
需要集中注入时再包装 Handler
如果团队要求所有调用点都自动补充同一组上下文字段,可以包装 Handler,在 Handle 阶段从 context 中读取白名单字段,再调用下层 Handler。这个方案要同时实现 WithAttrs 和 WithGroup 的委托,否则 logger 的分组或预格式化行为可能在包装层丢失。
type requestIDKey struct{}
type contextHandler struct {
next slog.Handler
}
func (h *contextHandler) Enabled(ctx context.Context, level slog.Level) bool {
// 先委托 Enabled,尽早过滤低级别日志,避免无谓构造字段。
return h.next.Enabled(ctx, level)
}
func (h *contextHandler) Handle(ctx context.Context, record slog.Record) error {
if requestID, ok := ctx.Value(requestIDKey{}).(string); ok && requestID != "" {
// 只读取明确白名单键,不遍历 context,也不记录完整用户对象。
record.AddAttrs(slog.String("request_id", requestID))
}
return h.next.Handle(ctx, record)
}
func (h *contextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
// 返回新的包装器,保持原 handler 不变,符合 slog 的不可变链式语义。
return &contextHandler{next: h.next.WithAttrs(attrs)}
}
func (h *contextHandler) WithGroup(name string) slog.Handler {
// 继续委托分组,避免请求字段与业务字段落入错误层级。
return &contextHandler{next: h.next.WithGroup(name)}
}
集中注入的优点是调用点少改,缺点是字段来源变得隐式,排查覆盖关系也更难。若同一键既由 logger.With 显式设置,又由 Handler 从 context 注入,要提前约定谁优先;生产代码还应给敏感字段做脱敏或直接禁止注入。

迁移后的回归检查
| 检查项 | 应确认的结果 | 常见误区 |
|---|---|---|
| 字段是否稳定 | 同一请求的多条日志都带同一个 request_id | 每个调用点重新读取请求头,导致字段遗漏或不一致 |
| context 是否真的传入 | 使用 InfoContext、LogAttrs 或 Log,而不是无 context 的快捷方法 | 以为调用 Info 就能让 Handler 读取请求 context |
| 并发是否隔离 | 基础 logger 只读共享,请求 logger 每请求新建 | 把请求字段写入全局 logger 或可变 map |
| 字段是否安全 | 只保留白名单,认证信息和完整用户对象不落盘 | 把 context 中所有值都当作可记录数据 |
| 格式是否兼容 | JSON 和文本输出都能按键检索,分组语义保持一致 | 包装 Handler 时漏掉 WithGroup 或覆盖字段顺序 |
小结
补充 Go slog 请求上下文字段时,推荐把“字段变成 Attr”的动作放在请求入口:从请求提取白名单字段,使用 Logger.With 创建请求级 logger,再在业务层用 InfoContext 或 LogAttrs 输出。只有在确实需要统一注入策略时,才让自定义 Handler 从 context 读取字段;无论选择哪种方案,都要保留 context 传递、并发隔离和敏感信息控制。
常见问题
只调用 slog.InfoContext 就会自动记录 context.Value 吗?
不会。它会把 context 传给 Handler,是否读取以及读取哪些键由 Handler 决定。普通 JSONHandler 不会遍历任意 context 值。
request_id 应该放在 context 还是 logger.With 里?
如果目标是让每条请求日志都出现,建议在入口提取后放进请求级 logger;context 可继续保存请求生命周期和需要被下游读取的值。两者职责不要混成“把 context 当日志字段容器”。
为什么不直接使用全局 slog.SetDefault?
全局默认 logger 适合应用级固定字段,但请求字段会随请求变化。把请求字段写入全局对象会产生串请求风险,应该使用请求级 logger 或 contextHandler。
Go fmt.Formatter 怎样实现自定义格式化
- 上一篇
- Go fmt.Formatter 怎样实现自定义格式化
- 下一篇
- Go slog 怎样让字段按模块分组
-
- Golang · Go教程 | 36分钟前 |
- Go reflect.Value.SetZero 怎样清空可变值
- 388浏览 收藏
-
- Golang · Go教程 | 39分钟前 |
- Go http.CrossOriginProtection 怎样给写接口加跨源请求保护
- 464浏览 收藏
-
- Golang · Go教程 | 42分钟前 |
- Go x509.CreateCertificate 怎样生成带 SAN 的本地测试证书
- 233浏览 收藏
-
- Golang · Go教程 | 50分钟前 |
- Go regexp.Regexp NumSubexp 怎样计算捕获组数量
- 269浏览 收藏
-
- Golang · Go教程 | 53分钟前 | 标准库 · go · Go HTTP客户端 连接复用 httptrace GotConnInfo
- Go httptrace.GotConnInfo 怎样判断连接是否复用
- 313浏览 收藏
-
- Golang · Go教程 | 54分钟前 |
- Go context 怎样复制值但不继承取消信号
- 275浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go fmt.Formatter 怎样实现自定义格式化
- 388浏览 收藏
-
- Golang · Go教程 | 1小时前 | cookie Go Authorization httputil.DumpRequestOut HTTP调试 请求头脱敏
- Go httputil.DumpRequestOut 怎样调试请求头而不泄露密钥
- 233浏览 收藏
-
- Golang · Go教程 | 1小时前 | rss Go GC debug.FreeOSMemory HeapReleased
- Go debug.FreeOSMemory 怎样判断释放是否真的有帮助
- 173浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 582次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 620次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 652次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 616次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 433次使用
-
- golang xorm 自定义日志记录器之使用zap实现日志输出、切割日志(最新)
- 2023-02-24 432浏览
-
- Go语言常用的打log方式详解
- 2023-02-24 485浏览
-
- Golang实现HTTP编程请求和响应
- 2022-12-28 101浏览
-
- golangNewRequest/gorequest实现http请求的示例代码
- 2023-01-24 343浏览
-
- 一文详解Golang中net/http包的实现原理
- 2022-12-29 419浏览

