Go log/slog 怎样为 slog 添加请求上下文字段
为 slog 添加请求字段,可以在请求入口从 context.Context 取出约定的请求标识,再用 base.With(slog.String("request_id", id)) 派生请求专属 Logger,向下传递并调用 InfoContext。传入 Context 与输出它里面的值是两件事:内置 JSONHandler 不会自动把任意 Context 值变成日志字段。
这个做法适合请求标识在入口确定、多个业务函数需要复用它的服务。下面使用标准库实现明确的字段绑定,不包装自定义 Handler,也不依赖第三方日志库。官方文档:https://pkg.go.dev/log/slog。
先确定谁负责把请求字段变成日志属性
把职责分成三层:Context 携带请求数据;入口函数选择允许记录的字段;Logger 复用已经绑定的属性。事件本身的属性仍在具体日志调用处补充。例如 service 属于基础 Logger,request_id 属于请求 Logger,operation 属于某条事件。
| 动作 | 负责什么 | 不负责什么 |
|---|---|---|
| Context.Value | 按约定键读取请求值 | 不是日志属性枚举器 |
| Logger.With | 返回绑定属性的 Logger | 不会修改原 Logger 的请求字段 |
| InfoContext | 把 Context 交给日志处理链 | 不承诺自动提取所有业务值 |

在请求入口派生 Logger 并向下传递
保存下面的完整程序为 main.go。requestIDKey 是本包私有键类型;字段提取函数只认识这一种键。实际服务应由可信入口取得或生成请求标识,再通过同一组封装函数设置和读取;不要让各业务包各自定义一个看似同名、实际不同的键类型。
package main
import (
"context"
"log/slog"
"os"
)
// 私有键类型避免与其他包常用的字符串键发生碰撞。
type requestIDKey struct{}
func withRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey{}, id)
}
func requestLogger(ctx context.Context, base *slog.Logger) *slog.Logger {
// 只提取约定字段,不尝试枚举整个 Context。
id, ok := ctx.Value(requestIDKey{}).(string)
if !ok || id == "" {
return base
}
return base.With(slog.String("request_id", id))
}
func handle(ctx context.Context, base *slog.Logger) {
// 每个请求绑定自己的值;base 始终不变。
logger := requestLogger(ctx, base)
logger.InfoContext(ctx, "请求进入")
lookup(ctx, logger)
}
func lookup(ctx context.Context, logger *slog.Logger) {
// 事件字段只属于本条日志,请求字段由 logger 复用。
logger.InfoContext(ctx, "查询准备", slog.String("operation", "lookup"))
}
func main() {
// 演示写到标准输出;日志器不拥有也不关闭这个输出流。
base := slog.New(slog.NewJSONHandler(os.Stdout, nil)).
With(slog.String("service", "catalog-demo"))
// 两个固定标识仅供示例,不是生产请求编号生成方案。
ctxA := withRequestID(context.Background(), "demo-A")
ctxB := withRequestID(context.Background(), "demo-B")
handle(ctxA, base)
handle(ctxB, base)
}
handle 同时接收 Context 与 Logger:前者保留请求取消和期限等语义,后者声明日志依赖。lookup 没有重新读取请求字段,因而不会在每个事件上重复实现键名、缺失值和字段白名单规则。若现有代码本来就显式传递请求 ID,也可以直接用该参数派生 Logger,不必为了日志把所有参数塞进 Context。
用两组请求观察字段是否串用
# 在 main.go 所在目录执行;本文未实际运行此示例。 go run main.go
按代码结构,正常输出时应有四条日志事件:demo-A 的两条带同一 request_id,demo-B 的两条带另一个 request_id,每条均带 service;只有“查询准备”事件带 operation。时间值由运行环境产生,不应与固定样本逐字比较。这是供读者运行后的判断条件,本文不提供虚构输出。
检查时重点看属性归属:若下层日志缺少 request_id,先看是否误用了 base 或包级 slog.InfoContext;若 A 的字段出现在 B 的记录里,先看请求 Logger 是否被保存成共享变量。示例故意顺序调用两个请求,不声称已经覆盖并发压测或输出故障。

避免把请求状态写进全局 Logger
不要在每个请求里调用 slog.SetDefault 来“切换当前请求”。默认 Logger 是进程范围的选择,不是请求局部变量。也不要忽略 base.With(...) 的返回值,然后继续使用 base 并期待它已经包含请求字段。把派生结果保存在局部变量中,依赖关系会更直接。
这种设计的代价是函数签名多一个 Logger 参数;收益是字段来源可见,避免隐式依赖。适合在入口固定的请求属性只绑定一次。如果认证后才得到新的可记录信息,应在那个边界派生新的 Logger 并传给后续函数;之前的 Logger 不会自动更新,之前的日志也不会被补写。
示例只放字符串属性。不要把整个请求、认证头、Cookie、请求体或可能被继续修改的业务对象直接交给 slog.Any。先确定字段白名单和脱敏策略;请求关联标识也不应直接当作高基数指标标签。本方案只做日志关联,不自动提供分布式追踪或日志写入可靠性保证。
请求日志的几个边界问题
没有 request_id 时怎么办?
示例返回 base,不伪造一个“unknown”冒充真实关联值。业务可以改为显式缺失标记,但要统一约定;错误类型或空字符串都会按缺失处理。
Context 取消后,日志一定不再输出吗?
不要把日志调用当作业务取消检查。需要停止业务时应单独检查取消信号;传 Context 的目的,是给处理链保留上下文,不保证它等同于日志开关。
后续再传一个 request_id 会覆盖原值吗?
不要依赖重复键实现覆盖。入口绑定后,事件层应禁止再写同名请求键,否则可能产生重复 JSON 键和下游解析歧义。需要新作用域时,明确字段名或派生策略。
落地时可以沿三条线检查自己的代码:入口是否只有一个字段提取约定;业务函数是否接收并使用正确 Logger;事件层是否只补充事件属性。Context 键与值的用途可参阅 https://pkg.go.dev/context#WithValue,slog 的设计背景见 https://go.dev/blog/slog。
Go database/sql 怎样让事务回滚覆盖提前返回路径
- 上一篇
- Go database/sql 怎样让事务回滚覆盖提前返回路径
- 下一篇
- Go log/slog ReplaceAttr 怎样统一改写时间和来源字段
-
- Golang · Go教程 | 59分钟前 | Go教程 · Go 日志级别 log/slog slog.Handler LevelVar HandlerOptions.Level Enabled
- Go log/slog 怎样让 Handler 过滤低级别日志
- 272浏览 收藏
-
- Golang · Go教程 | 1小时前 | 日志 · 标准库 · go · Go 结构化日志 ReplaceAttr log/slog HandlerOptions
- Go log/slog ReplaceAttr 怎样统一改写时间和来源字段
- 333浏览 收藏
-
- Golang · Go教程 | 1小时前 | 错误处理 · Go教程 · database/sql · Go commit 事务 rollback database/sql sql.Tx BeginTx
- Go database/sql 怎样让事务回滚覆盖提前返回路径
- 249浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · 数据库 · Go database/sql ColumnType Rows.ColumnTypes
- Go database/sql 怎样用 RowsColumnTypes 获取列元数据
- 171浏览 收藏
-
- Golang · Go教程 | 1小时前 | Go教程 · database/sql · Go database/sql rows.Next Rows.Scan sql.RawBytes 二进制字段
- Go sql.RawBytes 为什么只能在下一次 Next 前使用
- 159浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go reflect 怎样区分 IsZero 和无效反射值
- 477浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go reflect 怎样判断反射值是否可寻址
- 485浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go reflect 怎样用 StructOf 构造临时结构体
- 335浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go runtime 怎样读取进程内存统计快照
- 366浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go testing 怎样为子测试设置独立环境变量
- 480浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 514次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 572次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 598次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 550次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 368次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览
