Go slog 的 WithAttrs 为什么没有出现在文本输出里
Go 的 log/slog 中,logger.With("request_id", id) 不是把字段直接写进缓冲区,而是让 Logger 创建一个带新 Handler 的副本。标准 TextHandler 会在后续处理记录时输出这些属性;如果消息出现但 request_id 不见了,优先排查 Handler 链路和属性改写规则,不要先改日志格式。
- 标准 TextHandler 的 WithAttrs 会保留属性,Info 记录应能看到 request_id。
- 日志完全不出现,先看 HandlerOptions.Level;只有字段消失,再看 ReplaceAttr 和自定义 Handler。
- 包装 Handler 必须返回 inner.WithAttrs(attrs) 的新实例,不能原样返回接收者。
先用标准 TextHandler 划清问题边界
先把业务 Handler 换成标准实现,并用固定的 Info 级别打印一行。如果这段输出包含 request_id=abc-42,说明 slog 的属性继承没有问题,故障在原来的包装器、过滤器或调用路径中。
package main
import (
"context"
"os"
"log/slog"
)
func main() {
// 标准 TextHandler 会接收 Logger.With 传入的属性。
handler := slog.NewTextHandler(os.Stdout, nil)
logger := slog.New(handler).With("request_id", "abc-42")
// 使用 Info,避免默认 LevelInfo 把 Debug 记录提前过滤掉。
logger.InfoContext(context.Background(), "开始处理请求")
}
预期文本至少包含 level=INFO msg="开始处理请求" request_id=abc-42。如果整行没有输出,把问题归到级别过滤;如果整行存在但字段消失,继续看属性改写和 Handler 包装。

日志不出现和字段不出现是两种故障
HandlerOptions.Level 控制记录是否被处理。它不会只隐藏 request_id,而是让低于最低级别的整条记录都不进入 Handle。因此把最低级别设为 Warn 后,调用 logger.Info 看不到任何输出是正常现象。
另一条常见路径是 ReplaceAttr。它会在属性写出前接收已经解析的属性;如果针对 request_id 返回零值 slog.Attr{},这个字段就会被明确丢弃。调试时先临时移除 ReplaceAttr,再决定是否需要按分组路径过滤。
opts := &slog.HandlerOptions{
Level: slog.LevelInfo,
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
// 只隐藏密码,不要把所有业务属性误删。
if attr.Key == "password" {
return slog.Attr{}
}
return attr
},
}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts)).With("request_id", "abc-42")
logger.Info("登录检查")
如果回调里写了“非白名单都返回零值”,或只判断 groups 而忽略了属性键,WithAttrs 产生的字段就会被过滤掉。分组日志还要注意:WithGroup("request") 后,回调看到的路径会包含 request,不能继续按顶层字段名猜测。
自定义 Handler 必须复制属性继承关系
很多项目会用一个轻量包装器增加采样、路由或脱敏。这里最容易出现“消息有了,WithAttrs 没了”:包装器的 WithAttrs 直接返回了自己,或者创建了新包装器却把原始 inner 放了进去。
type routeHandler struct {
inner slog.Handler
}
func (h *routeHandler) Enabled(ctx context.Context, level slog.Level) bool {
// 级别判断交给内层 Handler,保持原有过滤策略。
return h.inner.Enabled(ctx, level)
}
func (h *routeHandler) Handle(ctx context.Context, record slog.Record) error {
// 这里可以做路由,但不要重新构造时丢掉 record 的属性。
return h.inner.Handle(ctx, record)
}
func (h *routeHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
// 必须把属性继续交给内层,并返回新的包装器。
return &routeHandler{inner: h.inner.WithAttrs(attrs)}
}
func (h *routeHandler) WithGroup(name string) slog.Handler {
// 分组也要沿同一条 Handler 链传递。
return &routeHandler{inner: h.inner.WithGroup(name)}
}
修复后不要直接调用 Handler 的方法做业务日志,仍然用 slog.New(h).With(...).Info(...),这样可以同时验证 Logger 副本、级别判断和 Handler 的属性继承。
按这张清单定位实际输出路径
| 现象 | 优先检查 | 处理方式 |
|---|---|---|
| 整行没有 | Level、Enabled、是否用了 Debug | 先换 Info 或降低 Level |
| 消息有,WithAttrs 没有 | 自定义 Handler.WithAttrs | 返回 inner.WithAttrs(attrs) 的新包装器 |
| 只有某些键没有 | ReplaceAttr、脱敏白名单 | 保留非目标键,检查 groups |
| 分组字段位置异常 | WithGroup 调用顺序 | 统一 logger.WithGroup 后再 With |
| 改了代码却无变化 | 实际使用的 Logger 实例 | 避免混用默认 Logger 和局部 Logger |

常见问题
WithAttrs 会影响 Handler.Enabled 吗?
不会。Enabled 负责判断某个级别是否处理,WithAttrs 负责构造带属性的 Handler;低级别记录会在属性处理前被整体跳过。
为什么把 Debug 改成 Info 后字段就出现了?
默认 TextHandler 的最低级别是 Info。Debug 记录被过滤时,字段并非单独丢失,而是整条记录没有进入 Handle。
WithGroup 后 ReplaceAttr 应该判断什么?
同时看 groups 和 attr.Key。同一个键在不同分组下可能代表不同字段,不能只用键名做全局删除。
标准 Handler 正常,自定义 Handler 该怎么回滚?
先临时换回标准 TextHandler 保证日志可见,再逐个恢复包装器;每恢复一层都用同一个 Info 记录检查 request_id 是否仍在。
排查这类问题时,先问“整条记录是否被处理”,再问“属性在哪一层被改写”。标准实现能输出字段,就说明重点不在文本格式,而在 Handler 链路、过滤规则和实际 Logger 实例。
GitHub Desktop 暂存部分修改后怎么查看未暂存差异
- 上一篇
- GitHub Desktop 暂存部分修改后怎么查看未暂存差异
- 下一篇
- LLM 流式输出中断时怎么保存可恢复的对话状态
-
- Golang · Go问答 | 7分钟前 | go · pprof · 性能排查 · 性能分析 Go pprof CPU profile
- Go pprof profile 采集时间太短看不到热点怎么办
- 122浏览 收藏
-
- Golang · Go问答 | 28分钟前 | go · DNS · resolver · 网络排查 · DNS Go GODEBUG net.Resolver LookupHost
- Go DNS 解析结果为什么和系统命令不一致
- 170浏览 收藏
-
- Golang · Go问答 | 42分钟前 | TLS · Go问答 · 网络排障 · 证书 · Go crypto/tls 证书链 ServerName TLS握手 SNI
- Go TLS 握手失败时怎么查看证书链和 SNI
- 397浏览 收藏
-
- Golang · Go问答 | 54分钟前 |
- Go CookieJar 为什么没有保存服务端返回的 Cookie
- 426浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go HTTP 重定向为什么改变了 POST 请求方法
- 216浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go gzip 解压时如何限制解压炸弹的输出大小
- 498浏览 收藏
-
- Golang · Go问答 | 2小时前 | Go问答 · 文件系统 · 路径安全 · tar解包 · 目录穿越 · Go archive/tar 安全解包 filepath.IsLocal 目录穿越
- Go tar 解包出现目录穿越时如何识别危险路径
- 286浏览 收藏
-
- Golang · Go问答 | 2小时前 | go · 安全校验 · 文件路径 · Go 路径安全 filepath.Clean
- Go filepath.Clean 为什么不能代替路径权限检查
- 437浏览 收藏
-
- Golang · Go问答 | 3小时前 |
- Go exec.Cmd 输出管道不读取为什么 Wait 会阻塞
- 259浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 173次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 103次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 31次使用
-
- LangGPT
- LangGPT是一种受编程语言启发的结构化提示词设计工具,提供双层框架、模块化模板及变量功能,帮助用户高效编写高质量Prompt。该项目已在GitHub免费开源,适用于内容创作、编程辅助等多场景。
- 41次使用
-
- ClickPrompt
- ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
- 77次使用
-
- Go语言Handler详细说明
- 2023-01-01 207浏览
-
- Go slog 生产实践:日志别只会打印 error,要能帮你排障
- 2026-06-01 143浏览
-
- Go slog 结构化日志实践:从请求字段到检索定位
- 2026-06-29 194浏览
-
- Go slog 结构化日志怎么落地:从 fmt.Println 到 JSON 日志的迁移路线
- 2026-07-08 219浏览
-
- Go slog 如何脱敏嵌套日志:ReplaceAttr、组字段与单元测试
- 2026-08-09 247浏览

