Go slog 如何给每条日志补 request_id:WithAttrs 与 Handler 包装的边界
接口日志里没有 request_id,排查一次慢请求往往要靠时间和猜测。Go 的 log/slog 已经提供了结构化字段能力,但固定字段和请求级动态字段并不是同一种问题:WithAttrs 适合把不会变化的上下文挂到 logger 上,真正随请求变化的值则应在请求边界注入,或者交给一个明确的 Handler 包装层处理。
最小原则是:服务名、版本、区域这类固定字段用
WithAttrs;request_id、用户标识和 trace 信息从请求上下文取得,避免把一个请求的状态错误地共享给其他请求。
- 先创建基础 Handler,再用
logger.With或WithAttrs固定服务级字段。 - 请求级字段放在 handler、middleware 或显式 logger 参数的边界,不能写进全局共享 logger 的可变状态。
- 用并发请求和字段断言验收:每条记录都应带自己的
request_id,且不能串号。

先把固定上下文和请求上下文分开
一条日志通常同时包含两类信息。服务名称、版本号和部署区域在进程生命周期内基本不变;请求编号、路由和操作者则随着每一次调用变化。把它们都塞进同一个全局 logger,短期看起来省事,遇到并发后就很难证明字段属于谁。
固定字段可以在构造 logger 时一次绑定:
base := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger := base.With(
slog.String("service", "checkout-api"),
slog.String("region", "cn-east-1"),
)
这里的 With 会把属性交给 Handler 的 WithAttrs 语义处理。它的价值是集中表达稳定上下文,而不是给后续请求留下一个可以被覆盖的“当前编号”。
request_id 应该在哪个边界注入
请求编号最适合在入口处生成或读取,然后沿着调用链显式传递。小型服务可以直接派生一个请求 logger,传给需要记录日志的函数:
func handleOrder(ctx context.Context, logger *slog.Logger) {
requestID, _ := ctx.Value(requestIDKey{}).(string)
reqLogger := logger.With(slog.String("request_id", requestID))
reqLogger.Info("开始处理订单")
// 后续函数继续接收 reqLogger,避免重新猜测请求上下文。
}
如果业务层已经统一使用 context.Context,也可以把 logger 放进 context,再由一个小函数取出。关键不是“必须使用哪种封装”,而是请求级 logger 的生命周期不能超过请求本身。
不要在全局 logger 上保存可变的 request_id。这种写法在单线程演示里可能正常,到了并发请求就会出现字段覆盖、串号和难以复现的日志。
什么时候需要包装 Handler
显式传 logger 的好处是依赖关系清楚;但如果项目里所有调用都经过统一的日志 Handler,包装层可以把“从上下文读取字段”的规则集中起来。包装 Handler 的 Handle 方法接收一条日志记录,在交给下层 Handler 之前补充属性:
type requestHandler struct {
next slog.Handler
}
func (h requestHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.next.Enabled(ctx, level)
}
func (h requestHandler) Handle(ctx context.Context, r slog.Record) error {
if id, ok := ctx.Value(requestIDKey{}).(string); ok && id != "" {
r.AddAttrs(slog.String("request_id", id))
}
return h.next.Handle(ctx, r)
}
func (h requestHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return requestHandler{next: h.next.WithAttrs(attrs)}
}
func (h requestHandler) WithGroup(name string) slog.Handler {
return requestHandler{next: h.next.WithGroup(name)}
}
包装层要完整转发 Enabled、WithAttrs 和 WithGroup。少实现一个,级别判断、分组字段或固定属性都可能和原 Handler 的行为不一致。
两种方案的取舍不要混成隐式魔法

如果只有少数入口需要 request_id,显式的 logger.With 更容易阅读和测试;如果日志来自很多公共库、又能保证每次调用都携带正确 context,Handler 包装更适合统一治理。
两者也可以组合:基础 logger 用 WithAttrs 固定服务字段,包装 Handler 只负责从当前 context 补请求字段。组合时要约定字段名唯一,避免同一条记录先后出现两个同名属性,导致下游采集平台的取值规则不一致。
用并发验收防止日志串号
不要只看一条漂亮的 JSON 输出。至少构造两个不同的 context,并发写入多条记录,然后检查每一条 request_id 是否来自当前 context。可以把 Handler 接到测试用的内存 Handler,断言记录数量、编号分布和固定字段。
验收时重点看三个结果:没有 request_id 的后台任务是否仍能正常记录;同一请求的多条日志编号是否一致;两个并发请求是否完全没有交叉编号。若使用异步日志队列,还要把 context 中的值在入队前解析成属性,不能把可能已经结束的 context 指针交给后台线程。
常见问题
WithAttrs 会修改原来的 logger 吗?
它返回带有附加属性的新 logger 或 Handler,调用方应把返回值保存下来使用。不要把它理解成给所有共享调用者设置“当前属性”。
request_id 放在 context 里就会自动出现在日志里吗?
不会。只有显式从 context 取出并调用 logger.With、Record.AddAttrs 或对应 Handler 逻辑,字段才会进入日志记录。
为什么包装 Handler 还要实现 WithGroup?
日志调用方可能使用分组属性。包装层如果不转发分组语义,固定字段和请求字段在 JSON 中的层级就可能不一致,查询规则也会变得不稳定。
小结
WithAttrs 解决的是稳定属性复用,Handler 包装解决的是统一处理动态请求上下文。把 request_id 的来源、生命周期和验收方式写清楚,再按项目规模选择显式传递或集中封装,日志系统才既方便查询,也不容易在并发下制造假线索。
雨夜图书馆窗边手机壁纸提示词:锁屏留白与琥珀蓝变体
- 上一篇
- 雨夜图书馆窗边手机壁纸提示词:锁屏留白与琥珀蓝变体
- 下一篇
- Go slog 如何按级别过滤日志:HandlerOptions、日志级别与测试验证
-
- Golang · Go教程 | 5小时前 |
- Go context.WithCancelCause 怎么传递取消原因:错误链与测试验收
- 284浏览 收藏
-
- Golang · Go教程 | 22小时前 | 内存 · 标准库 · go · 性能 · bytes.Buffer · go bytes.Buffer Buffer.Available Buffer.Grow ErrTooLarge
- Go bytes.Buffer.Available 怎么判断可写空间:Grow、Append 与 ErrTooLarge 边界
- 465浏览 收藏
-
- Golang · Go教程 | 1天前 | go · Context · 并发控制 · context取消 Go context.AfterFunc 并发清理
- Go context.AfterFunc 怎么避免清理竞态:Stop 返回值与并发验证
- 151浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 5222次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4728次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4676次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4934次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4893次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- golang xorm 自定义日志记录器之使用zap实现日志输出、切割日志(最新)
- 2023-02-24 432浏览
-
- Go语言常用的打log方式详解
- 2023-02-24 485浏览
-
- Go常用技能日志log包创建使用示例
- 2023-01-28 105浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览

