当前位置:首页 > 文章列表 > Golang > Go教程 > Go slog 怎样补充请求上下文字段

Go slog 怎样补充请求上下文字段

来源:17golang原创 2026-10-21 21:44:03 0浏览 收藏

Go 的 log/slog 里,InfoContext 会把 context.Context 交给 Handler,但不会自动把 context 中的任意值变成日志字段。要让每条请求日志稳定带上 request_id、HTTP 方法和路径,最稳妥的做法是在请求入口提取允许记录的字段,用 Logger.With 创建请求级 logger,再把它沿调用链传给业务代码。

记住两层职责:context 负责携带请求生命周期和值,slog Attr 负责形成结构化日志字段。只调用 InfoContext 并不能替代把字段显式加入 logger 或 Handler。
迁移要点
  • 已有请求上下文时优先使用 InfoContext、LogAttrs,让 Handler 能拿到 context。
  • 稳定、低敏的请求字段在入口用 logger.With 固定下来,业务层只接收请求级 logger。
  • 只有需要统一策略时才包装 Handler 读取 context,并明确字段白名单、覆盖顺序和并发边界。

先分清 context 与日志属性

slog.Logger.Log、LogAttrs 以及顶层的 InfoContext 等方法都接收 context。标准 Handler 可以从这个参数读取 trace ID 一类的值,但它不会猜测你的业务键,更不会把 context.WithValue 中的所有内容遍历成日志。这样设计可以避免把取消信号、私密对象或不适合序列化的数据意外写入日志。

Go slog 请求从 HTTP context 到请求级 logger 再到结构化日志字段的关系说明图
图1:请求上下文、请求级 logger 与 slog 结构化字段的关系说明图,不是运行截图。

请求入口先创建请求级 logger

把字段加入 logger 的位置应尽量靠近请求入口。下面的中间件只提取可公开记录的字段:请求 ID 来自受控的请求头或服务端生成值,方法和路径来自请求本身;不要把 Cookie、Authorization 或完整查询参数直接塞进日志。

package main

import (
    "context"
    "log/slog"
    "net/http"
    "os"
)

type loggerKey struct{}

func requestLogger(base *slog.Logger, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := r.Header.Get("X-Request-ID")
        if requestID == "" {
            requestID = "generated-by-service"
        }

        // 只把允许落盘的低敏字段放进请求级 logger,避免记录认证信息。
        reqLogger := base.With(
            slog.String("request_id", requestID),
            slog.String("method", r.Method),
            slog.String("path", r.URL.Path),
        )
        ctx := context.WithValue(r.Context(), loggerKey{}, reqLogger)

        // 把新的 context 绑定到请求,后续处理器可取出同一个 logger。
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func loggerFrom(ctx context.Context, fallback *slog.Logger) *slog.Logger {
    // 缺少请求 logger 时使用 fallback,避免后台任务因类型断言失败而崩溃。
    if logger, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok {
        return logger
    }
    return fallback
}

func main() {
    base := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{}))
    _ = requestLogger
    _ = loggerFrom
    _ = base
}

这里的 context 只保存请求级 logger,而不是把一组可变字段放进全局变量。每个请求得到一个新的 logger,Logger.With 返回新 logger,不会改变共享的基础 logger,因此并发请求之间不会互相串字段。

业务层用 InfoContext 保留请求语义

在处理器或服务方法中取出请求级 logger后,使用带 context 的方法写日志。这样 Handler 仍能获得取消状态、链路追踪等上下文;稳定字段由 logger 自己携带,临时字段用 slog.Attr 加到当前记录。

func handleOrder(w http.ResponseWriter, r *http.Request, base *slog.Logger) {
    logger := loggerFrom(r.Context(), base)

    // LogAttrs 只接收 Attr,适合固定字段类型并减少参数歧义。
    logger.LogAttrs(r.Context(), slog.LevelInfo, "开始处理订单",
        slog.String("order_id", "order-1001"),
    )

    // 记录结束状态时继续使用同一个请求级 logger,字段会自动跟随。
    logger.InfoContext(r.Context(), "订单处理完成",
        slog.Int("status", http.StatusOK),
    )
    w.WriteHeader(http.StatusOK)
}

使用 JSONHandler 时,结果会是结构化对象,例如包含 request_id、method、path 和 order_id 等键。换成 TextHandler 只改变序列化外观,不改变字段来源。对高频日志,LogAttrs 比交替传入键和值更容易保持类型清晰;不需要为了补字段而拼接字符串。

需要集中注入时再包装 Handler

如果团队要求所有调用点都自动补充同一组上下文字段,可以包装 Handler,在 Handle 阶段从 context 中读取白名单字段,再调用下层 Handler。这个方案要同时实现 WithAttrs 和 WithGroup 的委托,否则 logger 的分组或预格式化行为可能在包装层丢失。

type requestIDKey struct{}

type contextHandler struct {
    next slog.Handler
}

func (h *contextHandler) Enabled(ctx context.Context, level slog.Level) bool {
    // 先委托 Enabled,尽早过滤低级别日志,避免无谓构造字段。
    return h.next.Enabled(ctx, level)
}

func (h *contextHandler) Handle(ctx context.Context, record slog.Record) error {
    if requestID, ok := ctx.Value(requestIDKey{}).(string); ok && requestID != "" {
        // 只读取明确白名单键,不遍历 context,也不记录完整用户对象。
        record.AddAttrs(slog.String("request_id", requestID))
    }
    return h.next.Handle(ctx, record)
}

func (h *contextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
    // 返回新的包装器,保持原 handler 不变,符合 slog 的不可变链式语义。
    return &contextHandler{next: h.next.WithAttrs(attrs)}
}

func (h *contextHandler) WithGroup(name string) slog.Handler {
    // 继续委托分组,避免请求字段与业务字段落入错误层级。
    return &contextHandler{next: h.next.WithGroup(name)}
}

集中注入的优点是调用点少改,缺点是字段来源变得隐式,排查覆盖关系也更难。若同一键既由 logger.With 显式设置,又由 Handler 从 context 注入,要提前约定谁优先;生产代码还应给敏感字段做脱敏或直接禁止注入。

Go slog 基础 logger、请求级 logger、临时 Attr 与 contextHandler 的分层结构说明图
图2:基础 logger、请求级字段、单条 Attr 与 Handler 包装的分层说明图,不是运行截图。

迁移后的回归检查

检查项应确认的结果常见误区
字段是否稳定同一请求的多条日志都带同一个 request_id每个调用点重新读取请求头,导致字段遗漏或不一致
context 是否真的传入使用 InfoContext、LogAttrs 或 Log,而不是无 context 的快捷方法以为调用 Info 就能让 Handler 读取请求 context
并发是否隔离基础 logger 只读共享,请求 logger 每请求新建把请求字段写入全局 logger 或可变 map
字段是否安全只保留白名单,认证信息和完整用户对象不落盘把 context 中所有值都当作可记录数据
格式是否兼容JSON 和文本输出都能按键检索,分组语义保持一致包装 Handler 时漏掉 WithGroup 或覆盖字段顺序

小结

补充 Go slog 请求上下文字段时,推荐把“字段变成 Attr”的动作放在请求入口:从请求提取白名单字段,使用 Logger.With 创建请求级 logger,再在业务层用 InfoContext 或 LogAttrs 输出。只有在确实需要统一注入策略时,才让自定义 Handler 从 context 读取字段;无论选择哪种方案,都要保留 context 传递、并发隔离和敏感信息控制。

常见问题

只调用 slog.InfoContext 就会自动记录 context.Value 吗?

不会。它会把 context 传给 Handler,是否读取以及读取哪些键由 Handler 决定。普通 JSONHandler 不会遍历任意 context 值。

request_id 应该放在 context 还是 logger.With 里?

如果目标是让每条请求日志都出现,建议在入口提取后放进请求级 logger;context 可继续保存请求生命周期和需要被下游读取的值。两者职责不要混成“把 context 当日志字段容器”。

为什么不直接使用全局 slog.SetDefault?

全局默认 logger 适合应用级固定字段,但请求字段会随请求变化。把请求字段写入全局对象会产生串请求风险,应该使用请求级 logger 或 contextHandler。

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