当前位置:首页 > 文章列表 > Golang > Go教程 > Go slog.HandlerOptions AddSource 怎样输出调用位置

Go slog.HandlerOptions AddSource 怎样输出调用位置

来源:17golang原创 2026-10-22 21:45:04 0浏览 收藏

我在给 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 才会出现。

Logger、Record.PC、TextHandler、JSONHandler 与 source 属性的静态关系说明图
图1:结构说明图,查看 AddSource 记录调用位置并交给不同 Handler 输出的关系。

最小配置应该怎么写

先把 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;只有确实需要统一格式、保留业务调用者位置时,才值得采用这种包装方式。

业务函数、包装函数、Logger.Log、AddSource 与 source.File/source.Line 的静态关系说明图
图2:结构说明图,查看 Logger 包装与 source.File、source.Line 处理边界。

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 的复制边界。

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