当前位置:首页 > 文章列表 > Golang > Go问答 > Go log/slog Attr.Resolve 怎样展开 LogValuer 返回值

Go log/slog Attr.Resolve 怎样展开 LogValuer 返回值

来源:17golang原创 2026-10-25 06:37:47 0浏览 收藏

很多 Go 开发者会把这个问题叫作“slog.Attr.Resolve”,但标准库真正提供方法的是 slog.Value.Resolve:Attr 只是保存 Key 和 Value。当一个字段的值实现了 slog.LogValuer 时,Handler 应先对 attr.Value 调用 Resolve,再根据解析后的 Kind 做输出、脱敏或分组处理。

最小规则是:Resolve 会反复调用 LogValue,直到得到非 LogValuer 的 Value;如果最终得到 Group,只保证 Group 本身已经解析,Group 内部属性不会被再次递归展开。

先区分 Attr、Value 和 LogValuer

slog.Attr 是键值对,Attr.Value 的类型是 slog.Value。普通整数、字符串可以直接读取;自定义类型则可以实现 LogValue() slog.Value,把敏感字段替换掉,或者把多个字段组织成一个 Group。

因此,Handler 不能把 Attr 当成“可解析对象”,而应把解析边界放在 Value 上:

package main

import "log/slog"

// UserID 只把内部标识转换成适合日志输出的字符串。
type UserID string

// LogValue 是 LogValuer 的实现,返回值会被 Resolve 继续处理。
func (id UserID) LogValue() slog.Value {
	// 这里展示脱敏后的字段,避免 Handler 直接接触内部表示。
	return slog.StringValue("user:" + string(id))
}

func resolveAttr(attr slog.Attr) slog.Value {
	// Attr 没有 Resolve;需要解析的是它携带的 Value。
	return attr.Value.Resolve()
}
slog Attr、Value、LogValuer 和 Resolve 的静态关系说明图
图1:Attr、Value 与 LogValuer 的静态结构说明图;展示调用关系,不是截图或运行证据。

Resolve 怎样展开嵌套 LogValuer

Resolve 不只调用一次 LogValue。如果第一次返回的 slog.Value 仍然承载另一个 LogValuer,它会继续展开,直到返回普通 Kind。下面的例子把外层对象转换成另一个带业务字段的 LogValuer:

package main

import "log/slog"

type Order struct {
	ID string
}

type orderLogValue struct {
	order Order
}

// LogValue 返回一个 Group,让 Handler 继续按字段处理订单信息。
func (v orderLogValue) LogValue() slog.Value {
	// GroupValue 中的 Attr 是输出结构,不是新的日志调用。
	return slog.GroupValue(
		slog.String("order_id", v.order.ID),
		slog.String("source", "api"),
	)
}

func main() {
	attr := slog.Any("order", orderLogValue{order: Order{ID: "A-100"}})
	resolved := attr.Value.Resolve()

	// KindGroup 表示 LogValue 已经把对象转换成字段组。
	if resolved.Kind() == slog.KindGroup {
		for _, child := range resolved.Group() {
			// child.Value 已经属于 Group 内容;是否继续解析由 Handler 决定。
			_ = child
		}
	}
}

这里最容易误判的是“Resolve 会把整棵属性树都展开”。实际上官方语义只保证返回值本身不再是 KindLogValuer;如果返回的是 Group,Group 中每个 Attr 的 Value 不会由这次 Resolve 自动递归处理。自定义 Handler 如果需要统一处理嵌套字段,就要在遍历 Group 时明确决定是否再次调用 Resolve。

递归返回时为什么会得到错误值

LogValuer 可以错误地返回自身,或者几个类型互相返回,形成无限解析。标准库不会让 Handler 无限调用,而是在 LogValue 调用次数超过内部阈值后返回一个包含错误信息的 Value。它仍然是普通 Value,不会继续保持 KindLogValuer。

package main

import "log/slog"

type Loop struct{}

// LogValue 故意返回自身对应的 LogValuer,用来说明递归保护边界。
func (Loop) LogValue() slog.Value {
	// 真实代码不要这样写,否则 Resolve 会触发标准库的递归保护。
	return slog.AnyValue(Loop{})
}

func inspect(v slog.Value) string {
	resolved := v.Resolve()
	// 先观察 Kind,再决定调用 String、Group 等类型专用方法。
	if resolved.Kind() == slog.KindLogValuer {
		return "仍是 LogValuer"
	}
	return resolved.String()
}

生产代码不要依赖错误文本做业务分支。更稳妥的做法是记录解析后的 Kind,必要时把这个 Value 交给 Handler 的统一错误呈现逻辑。对于 Group,也不要直接假设所有子属性都已解析。

Resolve 的标量、Group、递归保护与 Handler 处理边界说明图
图2:Resolve 返回值与 Handler 处理边界的静态关系图;用于说明 Group 和递归保护,不代表现场输出。

放进自定义 Handler 时的处理顺序

自定义 Handler 的 Handle 方法通常逐个处理 slog.Record 中的 Attr。正确顺序是先取出 Attr,再解析 Value,最后根据 Kind 读取标量或 Group。不要直接对 Value.LogValuer() 做强制断言,也不要把 Group 当成已经完全解析的平面字段。

func handleAttr(attr slog.Attr) {
	// 先展开 LogValuer,避免把自定义类型直接写入输出。
	v := attr.Value.Resolve()

	switch v.Kind() {
	case slog.KindGroup:
		// Group 内的 Attr 仍需要按 Handler 的递归策略单独处理。
		for _, child := range v.Group() {
			handleAttr(child)
		}
	case slog.KindString:
		// 标量按 Kind 读取,避免把 Group 或错误值当成字符串。
		_ = v.String()
	default:
		// 其他 Kind 交给统一的格式化分支处理。
		_ = v
	}
}

常见边界速查

情况Resolve 的结果处理建议
普通字符串或数字保持原 Kind直接按 Kind 读取
一次 LogValue 返回标量得到标量 Value可以继续格式化或脱敏
LogValue 返回 Group得到 KindGroup遍历子 Attr 时再决定是否 Resolve
互相返回 LogValuer得到带错误的普通 Value记录异常并停止继续展开

相关问题

为什么不用直接调用 LogValue?

直接调用只处理一层,而且调用方还要自己判断返回值是否继续实现 LogValuer。Resolve 把多层展开和递归保护集中到标准库,Handler 只需要处理最终 Kind。

Resolve 会修改原来的 Attr 吗?

不会。它返回一个新的 slog.Value,Attr 本身仍是原来的键值对;如果要输出解析后的结果,应使用返回值而不是再次读取原 Attr.Value。

Group 的子字段为什么还可能需要 Resolve?

因为 Resolve 的保证只覆盖外层返回值。Group 内的 Attr 是否继续展开,属于 Handler 自己的递归策略。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go encoding/json/v2 format 为什么 format 标签不支持(参数形态)Go encoding/json/v2 format 为什么 format 标签不支持(参数形态)
上一篇
Go encoding/json/v2 format 为什么 format 标签不支持(参数形态)
Go CEL unknown 与运行错误在结果处理上有什么区别
下一篇
Go CEL unknown 与运行错误在结果处理上有什么区别
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    679次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    696次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    741次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    709次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    547次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码