当前位置:首页 > 文章列表 > Golang > Go问答 > Go slog 的 WithAttrs 为什么没有出现在文本输出里

Go slog 的 WithAttrs 为什么没有出现在文本输出里

来源:17golang原创 2026-09-07 19:21:15 0浏览 收藏

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 包装。

Go slog Logger WithAttrs、TextHandler 和文本输出之间的静态属性关系
图1:Logger 副本把 request_id 交给新的 Handler,TextHandler 在处理记录时将继承属性与消息一起组织成文本字段。

日志不出现和字段不出现是两种故障

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
Go slog 的级别过滤、ReplaceAttr 和自定义 Handler 属性边界关系
图2:把整条记录过滤、单个属性改写和包装 Handler 转发分成三个边界,能快速判断字段在哪一层消失。

常见问题

WithAttrs 会影响 Handler.Enabled 吗?

不会。Enabled 负责判断某个级别是否处理,WithAttrs 负责构造带属性的 Handler;低级别记录会在属性处理前被整体跳过。

为什么把 Debug 改成 Info 后字段就出现了?

默认 TextHandler 的最低级别是 Info。Debug 记录被过滤时,字段并非单独丢失,而是整条记录没有进入 Handle。

WithGroup 后 ReplaceAttr 应该判断什么?

同时看 groupsattr.Key。同一个键在不同分组下可能代表不同字段,不能只用键名做全局删除。

标准 Handler 正常,自定义 Handler 该怎么回滚?

先临时换回标准 TextHandler 保证日志可见,再逐个恢复包装器;每恢复一层都用同一个 Info 记录检查 request_id 是否仍在。

排查这类问题时,先问“整条记录是否被处理”,再问“属性在哪一层被改写”。标准实现能输出字段,就说明重点不在文本格式,而在 Handler 链路、过滤规则和实际 Logger 实例。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
GitHub Desktop 暂存部分修改后怎么查看未暂存差异GitHub Desktop 暂存部分修改后怎么查看未暂存差异
上一篇
GitHub Desktop 暂存部分修改后怎么查看未暂存差异
LLM 流式输出中断时怎么保存可恢复的对话状态
下一篇
LLM 流式输出中断时怎么保存可恢复的对话状态
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    173次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    103次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    31次使用
  • LangGPT提示词框架:结构化Prompt设计方法与开源工具指南
    LangGPT
    LangGPT是一种受编程语言启发的结构化提示词设计工具,提供双层框架、模块化模板及变量功能,帮助用户高效编写高质量Prompt。该项目已在GitHub免费开源,适用于内容创作、编程辅助等多场景。
    41次使用
  • ClickPrompt:AI提示词生成与优化工具,支持Stable Diffusion、ChatGPT及代码辅助
    ClickPrompt
    ClickPrompt是一款专为AI提示词编写者设计的开源在线工具,支持Stable Diffusion绘图、ChatGPT对话及GitHub Copilot代码辅助。提供Prompt自动生成、一键运行、社区分享及可视化优化功能,帮助用户高效获取精准AI输出。
    77次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码