免去繁琐的手动埋点,Gin 框架可观测性最佳实践
作者:牧思
背景
在云原生时代的今天,Golang 编程语言越来越成为开发者们的首选,而对于 Golang 开发者来说,最著名的 Golang Web 框架莫过于 Gin [ 1] 框架了,Gin 框架作为 Golang 编程语言官方的推荐框架 [ 2] ,其提供了丰富的路由与中间件功能,使得 Golang 开发者可以轻松地构建复杂的 Web 应用。对于如此重要的 Web 框架,如何去快速而全面地对 Gin 应用进行监控成为了一大难题,本文将着重介绍 Gin 框架官方推荐的几种可观测性方案并进行对比,从而得出 Gin 框架可观测性的最佳实践。
观测方案一览
Gin 官方提供了丰富的插件来帮助开发者快速地搭建 Web 应用,在官方提供的插件列表 [ 3] 中,提供了对 OpenTelemetry 的几种支持方案,分别是 SDK 手动埋点方案 [ 4] ,编译时注入方案 [ 5] ,以及 eBPF 方案 [6 ] ,下面分别来对官方推荐的三种观测方案进行实践:

前置准备
- 首先使用 Gin 框架编写一个简单的 Golang 应用:
package mainimport ("io""log""net/http""time""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("/hello-gin", func(c *gin.Context) {c.String(http.StatusOK, "hello\n")})go func() {_ = r.Run()}()// give time for auto-instrumentation to start uptime.Sleep(5 * time.Second)for {resp, err := http.Get("http://localhost:8080/hello-gin")if err != nil {log.Fatal(err)}body, err := io.ReadAll(resp.Body)if err != nil {log.Fatal(err)}log.Printf("Body: %s\n", string(body))_ = resp.Body.Close()// give time for auto-instrumentation to report signaltime.Sleep(5 * time.Second)}
}
- 根据文档 [ 7] 快速拉起 OpenTelemetry 的各种服务端依赖,比如 OpenTelemetry Collector,Jaeger,Prometheus 等等。
手动埋点
手动埋点方案即是利用了 Gin 框架的 Middleware 机制,在 Gin 的请求处理过程中为本次请求生成 span,我们需要基于以上代码进行改造:
const (SERVICE_NAME = ""SERVICE_VERSION = ""DEPLOY_ENVIRONMENT = ""HTTP_ENDPOINT = ""HTTP_URL_PATH = ""
)// 设置应用资源
func newResource(ctx context.Context) *resource.Resource {hostName, _ := os.Hostname()r, err := resource.New(ctx,resource.WithFromEnv(),resource.WithProcess(),resource.WithTelemetrySDK(),resource.WithHost(),resource.WithAttributes(semconv.ServiceNameKey.String(SERVICE_NAME), // 应用名semconv.ServiceVersionKey.String(SERVICE_VERSION), // 应用版本semconv.DeploymentEnvironmentKey.String(DEPLOY_ENVIRONMENT), // 部署环境semconv.HostNameKey.String(hostName), // 主机名),)if err != nil {log.Fatalf("%s: %v", "Failed to create OpenTelemetry resource", err)}return r
}func newHTTPExporterAndSpanProcessor(ctx context.Context) (*otlptrace.Exporter, sdktrace.SpanProcessor) {traceExporter, err := otlptrace.New(ctx, otlptracehttp.NewClient(otlptracehttp.WithEndpoint(HTTP_ENDPOINT),otlptracehttp.WithURLPath(HTTP_URL_PATH),otlptracehttp.WithInsecure(),otlptracehttp.WithCompression(1)))if err != nil {log.Fatalf("%s: %v", "Failed to create the OpenTelemetry trace exporter", err)}batchSpanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter)return traceExporter, batchSpanProcessor
}// InitOpenTelemetry OpenTelemetry 初始化方法
func InitOpenTelemetry() func() {ctx := context.Background()var traceExporter *otlptrace.Exportervar batchSpanProcessor sdktrace.SpanProcessortraceExporter, batchSpanProcessor = newHTTPExporterAndSpanProcessor(ctx)otelResource := newResource(ctx)traceProvider := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithResource(otelResource),sdktrace.WithSpanProcessor(batchSpanProcessor))otel.SetTracerProvider(traceProvider)otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))return func() {cxt, cancel := context.WithTimeout(ctx, time.Second)defer cancel()if err := traceExporter.Shutdown(cxt); err != nil {otel.Handle(err)}}
}func main() {r := gin.Default()// 初始化您的OpenTelemetrytp, err := InitOpenTelemetry()if err != nil {log.Fatal(err)}defer func() {if err := tp.Shutdown(context.Background()); err != nil {log.Printf("Error shutting down tracer provider: %v", err)}}()// 添加gin的OpenTelemetry中间件实现r.Use(otelgin.Middleware("my-server"))r.GET("/hello-gin", func(c *gin.Context) {c.String(http.StatusOK, "hello\n")})
}
通过在代码里面对 Gin 服务添加 OpenTelemetry 中间件,可以有效地收集到 Gin 应用本身的调用链路信息:

可以看到,手动接入的方案需要对代码进行比较大的改造,需要去手动引入依赖,初始化 SDK,并手动注入 middleware,此外,该方案只能收集到 Gin 应用本身的链路信息,对于 Gin 的上游和下游应用也需要进行代码的改造才能将整个链路进行打通和串联。
编译时注入自动埋点
除了手动埋点方案,官方还推荐了编译时自动注入方案来实现在零代码修改的观测方案,用户可以参考阿里巴巴开源的编译时自动插桩项目 [ 8] 对上述实例程序进行插桩:
step 1:下载 Golang Agent 二进制包
首先,可以进入主页 [ 9] 下载最新版本的 Golang Agent 二进制包。


step 2:使用 Golang Agent 二进制包编译 Golang 应用
在拥有了 Golang Agent 的二进制包后,即可使用该二进制包代替 go build 编译 Golang 应用的二进制程序。
otel-linux-amd64 go build .
在执行上述命令后,即可在对应应用的根目录下找到具有可观测能力的 Golang 二进制程序。
step 3:配置上报端点,运行二进制程序
最后,通过文档 [ 10] 配置观测数据的上报端点,并且启动上一步中编译出来的具有可观测能力的 Golang 二进制程序:


可以看到,编译出来的二进制 Golang 程序可以完整地展示出应用的调用链路。
除了链路,编译出来的二进制 Golang 程序还可以有效地收集 Gin 应用的运行时指标,比如 Gin 应用的调用耗时,GC 次数,内存申请次数等等:


eBPF 自动埋点
官方提供的最后一种 Gin 应用的观测办法是通过 OpenTelemetry 的 eBPF 方案进行自动埋点,eBPF 方式只需要在部署应用时在应用进程命名空间下添加一个特权级的 Sidecar 容器,特权级的 Sidecar 容器会自动捕捉应用容器产生的观测数据,并且进行上报。
我们还是对第一步中使用的简易 Golang 应用进行观测,在 Kubernetes 环境中部署以下 yaml:
apiVersion: apps/v1
kind: Deployment
metadata:labels:app.kubernetes.io/name: emojiapp.kubernetes.io/part-of: emojivotoapp.kubernetes.io/version: v11name: emojinamespace: emojivoto
spec:progressDeadlineSeconds: 600replicas: 1revisionHistoryLimit: 10selector:matchLabels:app: emoji-svcversion: v11template:metadata:labels:app: emoji-svcversion: v11spec:containers:- env:- name: HTTPvalue: '8080'image: 'registry.cn-hangzhou.aliyuncs.com/private-mesh/ginotel:latest'imagePullPolicy: Alwaysname: emoji-svcports:- containerPort: 8080name: grpcprotocol: TCPresources:requests:cpu: 100mterminationMessagePath: /dev/termination-logterminationMessagePolicy: File- env:- name: OTEL_GO_AUTO_TARGET_EXEvalue: /usr/local/bin/app- name: OTEL_EXPORTER_OTLP_ENDPOINTvalue: 'http://jaeger.default.svc:4318'- name: OTEL_SERVICE_NAMEvalue: emojivoto-emojiimage: >-ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:v0.19.0-alphaimagePullPolicy: IfNotPresentname: emojivoto-emoji-instrumentationresources: {}securityContext:privileged: truerunAsUser: 0terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilednsPolicy: ClusterFirstrestartPolicy: AlwaysschedulerName: default-schedulersecurityContext: {}shareProcessNamespace: trueterminationGracePeriodSeconds: 0
Gin 应用产生的观测数据将会被自动地收集并上报至 jaeger 中:


eBPF 方案看起来非常的美好,但是实际使用时却有着各种限制,比如其对于 Golang 的小版本非常的敏感,demo 中的应用,如果我们使用 Go 1.23.4 版本(升级 1 个小版本)来进行编译,eBPF 就将因为 Golang 的版本不匹配而无法收集到任何观测数据:

此外,eBPF 方案还有其他较多的限制,比如 client 传递的 HTTP Header 不能超过 8 个,又比如 eBPF 对操作系统的内核版本的要求较高等等,具体可以参照这篇文章。
观测方案对比

总的来说,手动埋点的自由度更高,但是接入和维护的成本也最高,适合技术能力强的用户自己完全控制。eBPF 自动埋点方案接入成本最低,但是随之而来的是性能的开销以及使用场景的各种限制。而编译时注入自动埋点的方案相对来说解决了前两种方案的各种问题,在降低了用户接入维护成本的同时也解决了插桩的性能,安全性等问题,某种程度上是目前最适合客户的 Gin 应用观测方案!
总结和展望
Golang Agent 成功解决了 Golang 应用监控中繁琐的手动埋点问题,并已商业化上线至阿里云公有云,为客户提供强大的监控能力。这项技术最初的设计初衷是为了让用户能够在不改动现有代码的前提下轻松地插入监控代码,从而实现对应用程序性能状态的实时监测与分析,但它的实际应用领域超越预期,包括服务治理、代码审计、应用安全、代码调试等,甚至在许多未被探索的领域中也展现出潜力。
我们已经将这项创新方案开源,并成功捐赠给 OpenTelemetry 社区 [ 11] 。开源不仅促进技术共享与提升,借助社区的力量还可以持续探索该方案在更多领域上的可能。
最后诚邀大家试用我们的商业化产品,并加入我们的钉钉群 (开源群:102565007776,商业化群:35568145) ,共同提升 Go 应用监控与服务治理能力。通过群策群力,我们相信能为 Golang 开发者社区带来更加优质的云原生体验。
相关链接:
[1] Gin
https://github.com/gin-gonic/gin
[2] 推荐框架
https://go.dev/doc/tutorial/web-service-gin
[3] 插件列表
https://github.com/gin-gonic/contrib
[4] SDK 手动埋点方案
https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin
[5] 编译时注入方案
https://github.com/alibaba/opentelemetry-go-auto-instrumentation
[6] eBPF 方案
https://github.com/open-telemetry/opentelemetry-go-instrumentation
[7] 文档
https://opentelemetry.io/docs/demo/kubernetes-deployment/
[8] 编译时自动插桩项目
https://github.com/alibaba/opentelemetry-go-auto-instrumentation
[9] 主页
https://github.com/alibaba/opentelemetry-go-auto-instrumentation
[10] 文档
https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
[11] OpenTelemetry 社区
https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation
相关文章:
免去繁琐的手动埋点,Gin 框架可观测性最佳实践
作者:牧思 背景 在云原生时代的今天,Golang 编程语言越来越成为开发者们的首选,而对于 Golang 开发者来说,最著名的 Golang Web 框架莫过于 Gin [ 1] 框架了,Gin 框架作为 Golang 编程语言官方的推荐框架 [ 2] &…...
构建大语言模型应用:简介(第一部分)
本专栏聚焦大语言模型(LLM)相关内容的解析,通过检索增强生成(RAG)应用的视角来进行。 本系列文章 简介(本文)数据准备句子转换器向量数据库搜索与检索大语言模型开源检索增强生成评估大语言模…...
PEmicro Multilink FX调试踩坑
文章目录 1.背景2 功能说明2.1 实时数据查看功能2.1 电压观测2.2 SWO功能 3 设置与支持 1.背景 既然使用了NXP的芯片,笔者就想使用一下它的专用调试器,这里先说一下,笔者是从朋友那里借了一个调试器,型号为PEmicro Multilink FX …...
主流大模型采用的架构、注意力机制、位置编码等汇总表
记录下主流大模型的一些核心知识点,包括: 架构注意力机制位置编码归一化激活函数模型参数 表中的一些模型已经是很久之前的了,比如表中并未收入 DeepSeek V3 中使用的MLA的注意力机制。先占个位,后续如果有更新的汇总表再来更…...
SpringBoot学习笔记3.27
目录 实战篇第二课 1.注册参数的校验: 学习过程中遇到的问题: 1.什么是正则表达式 2.怎么自定义异常? 1. 创建全局异常处理类 2. 定义响应对象 3. 使用 ExceptionHandler 4. 设置响应状态码 5. 返回统一响应 6. 测试全局异常处理 …...
亚马逊严查变体!正常变体突然被拆分?!
近期,亚马逊平台对变体合规性的审查力度再次升级,许多卖家因此遭遇了变体评论被拆分、账号受限甚至被封禁的困境。这一变化让不少卖家措手不及,原本正常的变体评论突然被拆分,子体的评价不再汇总显示,而是各自独立呈现…...
2025NCTF--Web
文章目录 Websqlmap-masterez_dashez_dash_revenge Web sqlmap-master 源码 from fastapi import FastAPI, Request from fastapi.responses import FileResponse, StreamingResponse import subprocessapp FastAPI()app.get("/") async def index():return File…...
如何破解软件自动化测试框架的维护难题
破解软件自动化测试框架的维护难题应从优化测试用例设计、加强脚本的模块化与复用性、提高自动化测试工具的选择与使用效率等方面入手。其中,加强脚本的模块化与复用性尤为关键,通过提高脚本的模块化程度,可以显著降低后续维护成本࿰…...
外星人入侵(python设计小游戏)
这个游戏简而言之就是操作一个飞机对前方的飞船进行射击,和一款很久之前的游戏很像,这里是超级低配版那个游戏,先来看看效果图: 由于设计的是全屏的,所以电脑不能截图。。。。 下面的就是你操控的飞船,上面…...
iOS rootless无根越狱检测方案
不同于安卓的开源生态,iOS一直秉承着安全性更高的闭源生态,系统中的硬件、软件和服务会经过严格审核和测试,来保障安全性与稳定性。 据FairGurd观察,虽然iOS系统具备一定的安全性,但并非没有漏洞,如市面上…...
单端信号差分信号
单端信号和差分信号是电路中常见的两种信号传输方式,它们在具体的应用场景和特点上有着明显的区别。下面就来详细说明一下单端信号和差分信号的区别。 首先,单端信号是指信号通过一个信号线传输,通常一个信号线携带一个信号。这种传输方式适…...
LLM 优化技术(1)——Scaled-Dot-Product-Attention(SDPA)
在 Transformer 中抛弃了传统的 CNN 和 RNN,整个网络结构完全由Scaled Dot Product Attention 和Feed Forward Neural Network组成。一个基于 Transformer 的可训练的神经网络可以通过堆叠 Transformer 的形式进行搭建,Attention is All You Need论文中通…...
AIGC-头条号长文项目创作智能体完整指令(DeepSeek,豆包,千问,Kimi,GPT)
Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各...
基于音频驱动的CATIA动态曲面生成技术解析
一、技术背景与创新价值 在工业设计领域,参数化建模与动态仿真的结合一直是研究热点。本文提出的音频驱动建模技术突破了传统参数调整方式,实现了音乐节奏与三维曲面的实时动态交互。该技术可广泛应用于以下场景: 艺术化产品设计…...
5-管理员-维护权限
在“后台”-“人员管理”-“权限”下,通过不同的操作按钮,按照权限分组对权限进行设置。操作部分的按钮依次为 视野维护:设置该分组可以查看、访问的视图。权限维护:设置分组成员可以操作的具体动作等所有在禅道中涉及的权限。成…...
全新升级 | Built For You Spring ‘25 发布,Fin 智能客服实现新突破!
图像识别、语音交互、任务自动化,立即体验智能客服蜕变! 上周,Intercom 举办了 Built For You Spring 25 发布会,正式揭晓了 AI Agent Fin 的一系列令人振奋的更新。Fin 正在以前所未有的速度革新客户支持模式——它已经成功解决了…...
turtle的九个使用
一 import turtle as t color [red,green,blue,orange,pink] for i in range(len(color)):t.penup()t.goto(-20070*i,0)t.pendown()t.pencolor(color[i])t.circle(50, steps 5) t.done()二 #在____________上补充代码 #不要修改其他代码import random as r import turtle a…...
营销库存系统设计方案
文章目录 一、营销库存系统设计方案1. 核心模块设计实时库存管理促销库存预占机制库存分层调度动态库存分配2. 技术架构示例二、技术难点与解决方案高并发下的数据一致性防超卖与恶意请求拦截多级库存同步延迟异常场景处理三、关键注意事项1.系统弹性…...
R002-云计算
1 概念 英文名:Cloud Computing 核心:云计算的核心概念就是以互联网为中心,在网站上提供快速且安全的云计算服务与数据存储,让每一个使用互联网的人都可以使用网络上的庞大计算资源与数据中心 2.分类 基础设施即服务(IaaS)它向…...
LeeCode 434. 字符串中的单词数
统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。 请注意,你可以假定字符串里不包括任何不可打印的字符。 示例: 输入: "Hello, my name is John" 输出: 5 解释: 这里的单词是指连续的不是空格的字符,所以 "…...
DriveDreamer动力学模块和博弈论优化器
DriveDreamer的动力学模块与博弈论优化器是其实现复杂场景下高保真重建与多智能体协同优化的核心技术组件。 一、动力学模块(NTGM) 功能定位:作为新轨迹生成模块(Novel Trajectory Generation Module, NTGM)…...
【AI编程学习之Python】第一天:Python的介绍
Python介绍 简介 Python是一种解释型、面向对象的语言。由吉多范罗苏姆(Guido van Rossum)于1989年发明,1991年正式公布。官网:www.python.org Python单词是"大蟒蛇”的意思。但是龟叔不是喜欢蟒蛇才起这个名字,而是正在追剧:英国电视喜剧片《蒙提派森的飞行马戏团》(Mo…...
西域平台商品详情接口设计与实现
接口描述: 该接口用于获取西域平台中指定商品的详细信息,包括商品名称、价格、库存、描述、图片等。 点击获取key和secret 接口地址: GET /api/product/detail 请求参数: 参数名 类型 是否必填 描述 productId st…...
如何让 history 记录命令执行时间?Linux/macOS 终端时间戳设置指南
引言:你真的会用 history 吗? 有没有遇到过这样的情况:你想回顾某个重要命令的执行记录,却发现 history 只列出了命令序号和内容,根本没有时间戳?这在运维排查、故障分析、甚至审计时都会带来极大的不便。 想象一下,你在服务器上误删了某个文件,但不知道具体是几点执…...
云原生四重涅槃·破镜篇:混沌工程证道心,九阳真火锻金身
【乾坤惊变混沌劫起】 "轰——!" 龙渊山巅突然雷云翻滚,九重天外传来梵音轰鸣。监察使手中玄光镜剧烈震颤,镜中映出骇人景象:原本井然有序的Service Mesh星轨竟自行扭曲,数十万Envoy边车化身血色修罗&#…...
04-SpringBoot3入门-配置文件(多环境配置)
1、简介 在 SpringBoot 中,不同的环境(如开发、测试、生产)可以编写对应的配置文件,例如数据库连接信息、日志级别、缓存配置等。在不同的环境中使用对应的配置文件。 2、配置环境 # 开发环境 zbj:user:username: root # 测试环…...
CodeSouler v1.15.0 版本更新
经过深度研发与全面优化,CodeSouler迎来了又一次重要升级。本次更新不仅提升了插件的智能化水平,更将“自主开发”从愿景变为现实! 无论你是在构建插件、部署工具链,还是希望提升开发效率,v1.15.0版本都将为你带来更流…...
windows第十八章 菜单、工具栏、状态栏
文章目录 创建框架窗口菜单菜单的风格通过资源创建菜单菜单的各种使用通过代码创建菜单在鼠标位置右键弹出菜单 CMenu常用函数介绍工具栏方式一,从资源创建工具栏方式二,代码创建 状态栏状态栏基础创建状态栏 创建框架窗口 手动创建一个空项目ÿ…...
EMC电源端传导干扰预测试
本实验需要在微波暗室里面进行,隔离外界干扰。 1.EMI接收机和人工电源网络的电源线都插在隔离变压器上面,隔离变压器的电源插在AC220上面 2.被测设备EUT的电源线接在人工电源网络上: 人工电源网络的信号输出端连接EMI接收机。 EMI接收机前面…...
94二叉树中序遍历解题记录
怎么说呢,以为这道题不用记录了,菜得吓到了自己。起因是这个遍历的递归一般是写两个函数完成,如下: func inorder(root *TreeNode, res *[]int) {if root nil {return}inorder(root.Left, res)*res append(*res, root.Val) // …...
