Go slog.HandlerOptions AddSource 怎样输出调用位置
来源:17golang原创
时间:2026-10-22 21:45:04 402浏览 收藏
我在给 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 的复制边界。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习