Go slog.HandlerOptions AddSource 怎样输出调用位置
我在给 Go 服务补结构化日志时,最先想要的不是更多字段,而是“这条日志到底从哪一行打出来”。slog.HandlerOptions 的 AddSource 就是为这个问题准备的:设置为 true 后,内置 Handler 会把日志调用的源文件和行号加入输出。官方资料入口:https://pkg.go.dev/log/slog#HandlerOptions。
AddSource 只负责让 Handler 生成 source 信息;TextHandler 会把它写成 文件:行号,JSONHandler 会写成结构化的 Source 记录。若日志函数再包了一层,调用位置可能变成包装函数,需要自己取得调用者的程序计数器。
AddSource 到底记录了什么
这个选项不是给日志消息自动追加一段字符串,而是让日志记录保留程序计数器,Handler 再根据它解析出调用位置。直接调用 logger.Info 时,通常能看到业务代码所在文件和行号;当调用经过自定义包装函数时,slog 反射调用栈得到的可能是包装函数那一行。
TextHandler 的结果适合人读,source 通常呈现为 source=service.go:42。JSONHandler 则把 source 作为结构化对象输出,便于采集端按 file、line 等字段检索。两者的共同点是:只有 Handler 开启 AddSource 且记录中存在源信息时,source 才会出现。

最小配置应该怎么写
先把 Handler 的输出目标和 AddSource 一起配置,再把 Handler 交给 Logger。这里用标准输出演示,代码中的中文注释只解释关键配置,不代表某次本机运行截图。
package main
import (
"log/slog"
"os"
)
func main() {
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
// 打开调用位置,TextHandler 会输出 source=文件:行号。
AddSource: true,
})
logger := slog.New(handler)
// 这条日志的调用位置由 slog 记录并交给 Handler 格式化。
logger.Info("开始处理订单", "order_id", 1001)
}
示例输出可以理解为下面这种形态,文件名和行号会随调用位置变化:
level=INFO source=service.go:18 msg="开始处理订单" order_id=1001
如果改用 slog.NewJSONHandler,字段仍然是结构化日志的一部分,不应再把整行文本拆分后猜测位置。实际项目应根据采集系统需要选择格式,而不是为了看到行号同时维护两套日志字符串。
包装 Logger 后 source 为什么会变化
这是最容易误判的地方。比如业务代码调用 Infof,而 Infof 内部再调用 logger.Info,slog 看到的直接日志调用点就是包装文件。官方包装示例的做法是先检查级别,再用 runtime.Callers 跳过 Callers 和包装函数本身,最后把得到的 PC 传给 slog.NewRecord。
func Infof(logger *slog.Logger, format string, args ...any) {
// 被过滤的日志不需要计算格式化结果或继续取调用栈。
if !logger.Enabled(context.Background(), slog.LevelInfo) {
return
}
var pcs [1]uintptr
// 跳过 runtime.Callers 和 Infof,保留 Infof 的调用者位置。
runtime.Callers(2, pcs[:])
record := slog.NewRecord(
time.Now(),
slog.LevelInfo,
fmt.Sprintf(format, args...),
pcs[0],
)
// 让原 Logger 的 Handler 继续负责 source 和最终格式化。
_ = logger.Handler().Handle(context.Background(), record)
}
包装函数还要考虑参数计算成本和级别过滤。直接使用 logger.Info 的简单场景不必为了“看起来更底层”自己重建 Record;只有确实需要统一格式、保留业务调用者位置时,才值得采用这种包装方式。

ReplaceAttr 和生产环境怎么取舍
ReplaceAttr 可以改写或删除内置的 source 属性。常见做法是只保留文件名,去掉机器上的绝对目录;也可以在某些采集链路已经提供调用位置时删除 source,避免重复字段。处理 source 时要先确认值的类型是 *slog.Source,不要把普通业务属性误当成内置字段。
开发排障、灰度发布和错误日志通常值得开启 AddSource;高吞吐的常规信息日志则要结合采样、级别和采集成本评估。它会带来调用栈定位与格式化开销,但是否值得不是固定结论,应该用实际日志量和性能剖析决定。
我会用下面这张清单收尾:
- Handler 是否真的使用了
HandlerOptions{AddSource: true}。 - 输出端需要 TextHandler 的文本字段,还是 JSONHandler 的结构化 source。
- 是否存在包装函数;若存在,是否需要用
runtime.Callers修正调用者位置。 ReplaceAttr是否会清洗路径、改名或删除 source。- 生产环境是否有采样、级别过滤和日志成本控制。
相关问题
AddSource 为什么没有输出 source?
先看创建 Handler 时传入的 options 是否为 nil 或仍是默认值,再确认使用的 Handler 是 TextHandler/JSONHandler 等会处理 source 的实现,并检查 Record 是否带有有效的程序计数器。
能不能只输出文件名不输出完整路径?
可以在 ReplaceAttr 中识别 slog.SourceKey,复制或修改 Source 后只保留文件名。不要直接修改可能被其他处理逻辑共享的对象,包装自定义 Handler 时也要遵守 Record 的复制边界。
Go json.SyntaxError Offset 怎样找到 JSON 语法错误位置
- 上一篇
- Go json.SyntaxError Offset 怎样找到 JSON 语法错误位置
- 下一篇
- Go strconv.AppendBool 怎样低分配地追加布尔值
-
- Golang · Go教程 | 21分钟前 |
- Go slog 怎样给结构化日志加入请求字段
- 158浏览 收藏
-
- Golang · Go教程 | 24分钟前 |
- Go url.JoinPath 怎样避免手写斜杠错误
- 245浏览 收藏
-
- Golang · Go教程 | 24分钟前 | go · url ·
- Go net/url 怎样拆解并重建地址
- 194浏览 收藏
-
- Golang · Go教程 | 27分钟前 | 错误处理 · go · Go 编码转换 十六进制 io.Reader hex.NewDecoder
- Go hex.NewDecoder 怎样读取十六进制输入
- 284浏览 收藏
-
- Golang · Go教程 | 30分钟前 | Go strconv FormatFloat 缓冲区复用 AppendFloat
- Go strconv.FormatFloat 与 AppendFloat 怎样选择
- 191浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Go regexp.FindAllString 怎样保留每次完整匹配文本
- 416浏览 收藏
-
- Golang · Go教程 | 30分钟前 | 文件处理 · go · Go 文件编码 io.Copy base64.NewEncoder
- Go base64.NewEncoder 怎样流式编码文件
- 236浏览 收藏
-
- Golang · Go教程 | 32分钟前 | 标准库 · go · Go strconv.AppendFloat 浮点格式化
- Go strconv.AppendFloat 怎样控制格式化精度
- 300浏览 收藏
-
- Golang · Go教程 | 47分钟前 | Go iter.Seq slices.AppendSeq 切片追加
- Go slices.AppendSeq 怎样把迭代器追加到已有切片
- 473浏览 收藏
-
- Golang · Go教程 | 51分钟前 |
- Go bytes.FieldsFunc 怎样按 Unicode 规则切分文本
- 139浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 592次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 630次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 662次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 631次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 446次使用
-
- 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浏览

