当前位置:首页 > 文章列表 > Golang > Go教程 > Go log/slog 怎样为 slog 添加请求上下文字段

Go log/slog 怎样为 slog 添加请求上下文字段

来源:17golang原创 2026-10-18 07:31:21 0浏览 收藏

为 slog 添加请求字段,可以在请求入口从 context.Context 取出约定的请求标识,再用 base.With(slog.String("request_id", id)) 派生请求专属 Logger,向下传递并调用 InfoContext。传入 Context 与输出它里面的值是两件事:内置 JSONHandler 不会自动把任意 Context 值变成日志字段。

这个做法适合请求标识在入口确定、多个业务函数需要复用它的服务。下面使用标准库实现明确的字段绑定,不包装自定义 Handler,也不依赖第三方日志库。官方文档:https://pkg.go.dev/log/slog。

先确定谁负责把请求字段变成日志属性

把职责分成三层:Context 携带请求数据;入口函数选择允许记录的字段;Logger 复用已经绑定的属性。事件本身的属性仍在具体日志调用处补充。例如 service 属于基础 Logger,request_id 属于请求 Logger,operation 属于某条事件。

动作负责什么不负责什么
Context.Value按约定键读取请求值不是日志属性枚举器
Logger.With返回绑定属性的 Logger不会修改原 Logger 的请求字段
InfoContext把 Context 交给日志处理链不承诺自动提取所有业务值
Context 约定字段与请求 Logger 属性绑定的静态关系
图1:请求字段绑定结构图。数据约定与日志对象分组表示职责边界,无箭头连线只表示读取或绑定关系,不表示实测执行过程。

在请求入口派生 Logger 并向下传递

保存下面的完整程序为 main.go。requestIDKey 是本包私有键类型;字段提取函数只认识这一种键。实际服务应由可信入口取得或生成请求标识,再通过同一组封装函数设置和读取;不要让各业务包各自定义一个看似同名、实际不同的键类型。

package main

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

// 私有键类型避免与其他包常用的字符串键发生碰撞。
type requestIDKey struct{}

func withRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, requestIDKey{}, id)
}

func requestLogger(ctx context.Context, base *slog.Logger) *slog.Logger {
    // 只提取约定字段,不尝试枚举整个 Context。
    id, ok := ctx.Value(requestIDKey{}).(string)
    if !ok || id == "" {
        return base
    }
    return base.With(slog.String("request_id", id))
}

func handle(ctx context.Context, base *slog.Logger) {
    // 每个请求绑定自己的值;base 始终不变。
    logger := requestLogger(ctx, base)
    logger.InfoContext(ctx, "请求进入")
    lookup(ctx, logger)
}

func lookup(ctx context.Context, logger *slog.Logger) {
    // 事件字段只属于本条日志,请求字段由 logger 复用。
    logger.InfoContext(ctx, "查询准备", slog.String("operation", "lookup"))
}

func main() {
    // 演示写到标准输出;日志器不拥有也不关闭这个输出流。
    base := slog.New(slog.NewJSONHandler(os.Stdout, nil)).
        With(slog.String("service", "catalog-demo"))

    // 两个固定标识仅供示例,不是生产请求编号生成方案。
    ctxA := withRequestID(context.Background(), "demo-A")
    ctxB := withRequestID(context.Background(), "demo-B")
    handle(ctxA, base)
    handle(ctxB, base)
}

handle 同时接收 Context 与 Logger:前者保留请求取消和期限等语义,后者声明日志依赖。lookup 没有重新读取请求字段,因而不会在每个事件上重复实现键名、缺失值和字段白名单规则。若现有代码本来就显式传递请求 ID,也可以直接用该参数派生 Logger,不必为了日志把所有参数塞进 Context。

用两组请求观察字段是否串用

# 在 main.go 所在目录执行;本文未实际运行此示例。
go run main.go

按代码结构,正常输出时应有四条日志事件:demo-A 的两条带同一 request_id,demo-B 的两条带另一个 request_id,每条均带 service;只有“查询准备”事件带 operation。时间值由运行环境产生,不应与固定样本逐字比较。这是供读者运行后的判断条件,本文不提供虚构输出。

检查时重点看属性归属:若下层日志缺少 request_id,先看是否误用了 base 或包级 slog.InfoContext;若 A 的字段出现在 B 的记录里,先看请求 Logger 是否被保存成共享变量。示例故意顺序调用两个请求,不声称已经覆盖并发压测或输出故障。

基础服务属性请求标识事件属性与 JSON 输出的结构
图2:日志属性归属结构图。service、request_id 与 operation 分别对应不同作用域,JSONHandler 负责格式化,不负责业务字段提取。

避免把请求状态写进全局 Logger

不要在每个请求里调用 slog.SetDefault 来“切换当前请求”。默认 Logger 是进程范围的选择,不是请求局部变量。也不要忽略 base.With(...) 的返回值,然后继续使用 base 并期待它已经包含请求字段。把派生结果保存在局部变量中,依赖关系会更直接。

这种设计的代价是函数签名多一个 Logger 参数;收益是字段来源可见,避免隐式依赖。适合在入口固定的请求属性只绑定一次。如果认证后才得到新的可记录信息,应在那个边界派生新的 Logger 并传给后续函数;之前的 Logger 不会自动更新,之前的日志也不会被补写。

示例只放字符串属性。不要把整个请求、认证头、Cookie、请求体或可能被继续修改的业务对象直接交给 slog.Any。先确定字段白名单和脱敏策略;请求关联标识也不应直接当作高基数指标标签。本方案只做日志关联,不自动提供分布式追踪或日志写入可靠性保证。

请求日志的几个边界问题

没有 request_id 时怎么办?

示例返回 base,不伪造一个“unknown”冒充真实关联值。业务可以改为显式缺失标记,但要统一约定;错误类型或空字符串都会按缺失处理。

Context 取消后,日志一定不再输出吗?

不要把日志调用当作业务取消检查。需要停止业务时应单独检查取消信号;传 Context 的目的,是给处理链保留上下文,不保证它等同于日志开关。

后续再传一个 request_id 会覆盖原值吗?

不要依赖重复键实现覆盖。入口绑定后,事件层应禁止再写同名请求键,否则可能产生重复 JSON 键和下游解析歧义。需要新作用域时,明确字段名或派生策略。

落地时可以沿三条线检查自己的代码:入口是否只有一个字段提取约定;业务函数是否接收并使用正确 Logger;事件层是否只补充事件属性。Context 键与值的用途可参阅 https://pkg.go.dev/context#WithValue,slog 的设计背景见 https://go.dev/blog/slog。

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