使用 OpenTelemetry 和 Golang

news2024/11/20 15:41:50

入门

img

在本文中,我将展示你需要配置和处理统计信息所需的基本代码。在这个简短的教程中,我们将使用 Opentelemetry 来集成我们的 Golang 代码,并且为了可视化,我们将使用 Jeager。

在开始之前,让我简要介绍一下什么是 Opentelemetry。

什么是 Opentelemetry?

OpenTelemetry 是一个开源项目,旨在促进微服务和分布式应用的可观察性和可追踪性。它提供了一种标准化的方式来实现、生成记录并收集我们服务的指标,并且可以在不同的编程语言中实现。

其主要目标之一是解决以一致可靠的方式从分布式系统中收集遥测数据的挑战。Opentelemetry 是如何实现的呢?这项技术提供了库和工具,允许开发人员自动向他们的应用程序和组件添加工具,从而轻松生成和收集遥测数据,比如响应时间、延迟、错误、执行跟踪和性能指标等信息。

注意:
你可以在以下官方链接中找到更多有关 Opentelemetry 的信息:
https://opentelemetry.io/docs/what-is-opentelemetry/

img

我之前提到的另一个技术是 Jaeger。虽然它不是本文的中心话题,但我们将使用它来可视化收集的遥测数据。

Jaeger 是一个用于监视和调试分布式系统的开源跟踪平台。它为请求在分布式环境中传播时提供跟踪和追踪功能。

它的主要功能是捕获、存储和显示关于分布式系统中请求流动的详细信息。这包括有关运行时、依赖关系以及单个组件之间的交互的信息。

注意:
你可以在官方网站中找到关于 Jaeger 的更多信息:https://www.jaegertracing.io/docs/1.45/

实施时间!

现在你已经了解了 OpenTelemetry 和 Jeager,是时候构建两个简单的应用程序了。

  • 第一个将向我们展示如何在我们的 Golang 应用程序中配置 OpenTelemetry,并通过运行一些函数来学习。在这里,我们将了解跟踪的工作原理,以及如何在 Jeager 上可视化它们。
  • 第二个是基于 gin-gonic 框架的服务实现。

我们开始吧!

首先,我们需要运行一个 Jeager 实例,在这种情况下,我们将使用 Docker 使其变得简单。

Jaeger 提供了一个我们可以用于教程的 all-in-one 发行版。

docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 14250:14250 \
  -p 14268:14268 \
  -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.45

注意:对于生产环境,你应该根据需要进行适当的配置。

运行并构建容器后,你可以通过以下链接访问 Jaeger:http://127.0.0.1:16686/search。

你将看到如下页面:

img

持续运行并让我们开始编码吧。

集成 Opentelemetry

让我们从一个简单的示例开始:

我们需要以下依赖:

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"

你需要配置应用程序以将遥测数据发送到 Jaeger。此函数使用默认的 Jaeger URL:PORT 创建导出器。

我将尽量添加良好的注释,以指导你理解代码。

首先,定义下面几个常量,我们将在配置 OpenTelemetry 和导出器的函数中使用它们。

  • Service:帮助我们识别生成 span 的服务。
  • Environment:帮助我们按部署(例如生产、测试、开发)对服务 span 进行分组。
  • ID:帮助我们标识 span 组。
const (
 service     = "medium-tutorial"
 environment = "development"
 id          = 1
)

然后是函数:

func tracerProvider() (*tracesdk.TracerProvider, error) {
 // 创建 Jaeger 导出器
 exp, err := jaeger.New(jaeger.WithCollectorEndpoint())
 if err != nil {
  return nil, err
 }
 tp := tracesdk.NewTracerProvider(
  // 在生产环境中始终确保进行批处理。
  tracesdk.WithBatcher(exp),
  // 记录有关此应用程序的信息到资源中。
  tracesdk.WithResource(resource.NewWithAttributes(
   semconv.SchemaURL,
   semconv.ServiceName(service),
   attribute.String("environment", environment),
   attribute.Int64("ID", id),
  )),
 )
 return tp, nil
}

正如你所见,在此我们创建了导出器,同时我们使用了 Jeager(你可以在官方网站上查看完整的导出器列表)。之后,我们开始配置跟踪器提供程序,在那里我们定义了诸如 exporterserviceName 等参数,这些参数有助于在 Jaeger 上识别指标,并且像 environment 这样的参数有助于我们识别部署情况。

对于 main 方法,我们需要定义类似下面这样的代码示例:

tp, err := tracerProvider()
 if err != nil {
  log.Fatal(err)
 }

 // Register our TracerProvider as the global so any imported
 // instrumentation in the future will default to using it.
 otel.SetTracerProvider(tp)

 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()

 // Cleanly shutdown and flush telemetry when the application exits.
 defer func(ctx context.Context) {
  // Do not make the application hang when it is shutdown.
  ctx, cancel = context.WithTimeout(ctx, time.Second*5)
  defer cancel()
  if err := tp.Shutdown(ctx); err != nil {
   log.Fatal(err)
  }
 }(ctx)

 tr := tp.Tracer("component-main")
 ctx, span := tr.Start(ctx, "foo")
 defer span.End()

代码并不复杂,我们只是利用创建连接的函数,然后需要创建一个上下文。

我们的指标从这里开始:

tr := tp.Tracer("component-main")
ctx, span := tr.Start(ctx, "foo")
defer span.End()

这些代码行将帮助我们启动 span,跟踪器是主要组件,span 名称为 foo,并且你可以看到我们捕获了上下文,因为我们将使用该上下文来分组 span。(别担心,我会向你展示在 Jaeger 中的结果)

最后,这是完整的代码:

package main

import (
 "context"
 "fmt"
 "log"
 "time"

 "go.opentelemetry.io/otel"
 "go.opentelemetry.io/otel/attribute"
 "go.opentelemetry.io/otel/exporters/jaeger"
 "go.opentelemetry.io/otel/sdk/resource"
 tracesdk "go.opentelemetry.io/otel/sdk/trace"
 semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

const (
 service     = "medium-tutorial"
 environment = "development"
 id          = 1
)

// tracerProvider returns an OpenTelemetry TracerProvider configured to use
// the Jaeger exporter that will send spans to the provided url. The returned
// TracerProvider will also use a Resource configured with all the information
// about the application.
func tracerProvider() (*tracesdk.TracerProvider, error) {
 // Create the Jaeger exporter
 exp, err := jaeger.New(jaeger.WithCollectorEndpoint())
 if err != nil {
  return nil, err
 }
 tp := tracesdk.NewTracerProvider(
  // Always be sure to batch in production.
  tracesdk.WithBatcher(exp),
  // Record information about this application in a Resource.
  tracesdk.WithResource(resource.NewWithAttributes(
   semconv.SchemaURL,
   semconv.ServiceName(service),
   attribute.String("environment", environment),
   attribute.Int64("ID", id),
  )),
 )
 return tp, nil
}

func main() {
 tp, err := tracerProvider()
 if err != nil {
  log.Fatal(err)
 }

 // Register our TracerProvider as the global so any imported
 // instrumentation in the future will default to using it.
 otel.SetTracerProvider(tp)

 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()

 // Cleanly shutdown and flush telemetry when the application exits.
 defer func(ctx context.Context) {
  // Do not make the application hang when it is shutdown.
  ctx, cancel = context.WithTimeout(ctx, time.Second*5)
  defer cancel()
  if err := tp.Shutdown(ctx); err != nil {
   log.Fatal(err)
  }
 }(ctx)

 tr := tp.Tracer("component-main")
 ctx, span := tr.Start(ctx, "foo")
 defer span.End()

 bar(ctx)

 time.Sleep(10 * time.Second)
}

func bar(ctx context.Context) {
 fmt.Println("on bar")
 tr := otel.Tracer("component-bar")
 _, span := tr.Start(ctx, "bar")
 span.SetAttributes(attribute.Key("medium_test").String("this is an attribute value"))
 defer span.End()

 time.Sleep(200 * time.Millisecond)

}

如果我们运行程序,然后转到 Jaeger 的主页面,可以通过选择以下参数来搜索我们的统计信息:

服务:medium-tutorial
操作:所有

img

在上面的图像中,您可以看到当前的跨度。如果单击其中一个,将会看到更多信息:

img

img

在那里,您可以看到上面的注释行,您可以可视化跨度及其子跨度,这非常有帮助,因为可以追踪您的逻辑传播和流程。您可以在那里可视化的其他重要指标是每个过程(span)完成所花费的时间。

这里我想留下一条注释:如果您想了解更多有关跨度属性的信息,建议您查阅官方文档,本文只是一个简单的教程。

很简单,对吧?现在我们知道如何发送跨度以及如何对它们进行分组,我们可以将其实现到我们的 gin-gonic 实现中。让我们看一下它是什么样子的。

在 gin-gonic 服务中实现 OpenTelemetry

在以下代码中,您将找到一个简单的 gin-gonic 的 open telemetry 实现,我们可以重用用于配置导出器的基本配置。

此外,在此代码中,您将了解如何处理 Go 协程跨度。

以下是完整的代码:

package main

import (
 "context"
 "fmt"
 "log"
 "net/http"
 "time"

 "github.com/gin-gonic/gin"

 "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
 "go.opentelemetry.io/otel"
 "go.opentelemetry.io/otel/attribute"
 "go.opentelemetry.io/otel/exporters/jaeger"
 tracesdk "go.opentelemetry.io/otel/sdk/trace"

 "go.opentelemetry.io/otel/propagation"
 "go.opentelemetry.io/otel/sdk/resource"
 semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
 oteltrace "go.opentelemetry.io/otel/trace"
)

const (
 service     = "medium-gin-server-test"
 environment = "development"
 id          = 1
)

var tracer = otel.Tracer(service)

func tracerProvider() (*tracesdk.TracerProvider, error) {
 // Create the Jaeger exporter
 exp, err := jaeger.New(jaeger.WithCollectorEndpoint())
 if err != nil {
  return nil, err
 }
 tp := tracesdk.NewTracerProvider(
  // Always be sure to batch in production.
  tracesdk.WithSampler(tracesdk.AlwaysSample()),
  tracesdk.WithBatcher(exp),
  tracesdk.WithResource(resource.NewWithAttributes(
   semconv.SchemaURL,
   semconv.ServiceName(service),
   attribute.String("environment", environment),
   attribute.Int64("ID", id),
  ),
  ),
 )

 // Register our TracerProvider as the global so any imported
 // instrumentation in the future will default to using it.
 otel.SetTracerProvider(tp)
 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

 return tp, nil
}

func main() {

 fmt.Println("initializing")

 tp, err := tracerProvider()
 if err != nil {
  log.Fatal(err)
 }

 // Cleanly shutdown and flush telemetry when the application exits.
 defer func() {
  if err := tp.Shutdown(context.Background()); err != nil {
   log.Fatal(err)
  }
 }()

 r := gin.New()
 r.Use(otelgin.Middleware("my-server"))
 loadRoutes(r)

 r.Run()
}

func loadRoutes(r *gin.Engine) {
 r.GET("/ping", pingFunc)
}

func pingFunc(c *gin.Context) {

 ctx, span := tracer.Start(c.Request.Context(), "/ping", oteltrace.WithAttributes(attribute.String("hello", "the user")))
 defer span.End()

 bar(ctx)

 c.JSON(http.StatusOK, gin.H{
  "message": "pong",
 })
}

func ping2Func(c *gin.Context) {
 ctx, span := tracer.Start(c.Request.Context(), "/ping-2", oteltrace.WithAttributes(attribute.String("hello2", "the user 2")))
 defer span.End()

 bar(ctx)

 c.JSON(http.StatusOK, gin.H{
  "message": "pong",
 })
}

func bar(ctx context.Context) {
 fmt.Println("on bar")
 // Use the global TracerProvider.
 ct, span := tracer.Start(ctx, "bar")
 span.SetAttributes(attribute.Key("testset").String("value"))
 defer span.End()

 time.Sleep(1 * time.Millisecond)

 go bar3(ct)
}

func bar3(ctx context.Context) {
 fmt.Println("on bar 3")

 _, span := tracer.Start(ctx, "bar-3-on-goroutine")
 span.AddEvent("starting goroutine bar3")

 defer func() {
  span.End()
 }()
 span.AddEvent("executing logic")
 time.Sleep(1 * time.Second)

 span.AddEvent("completed goroutine bar3")
}

运行代码并向 ping 端点发出请求后,您可以转到 Jaeger 并搜索跨度。

img

这将显示类似于以下的内容:

img

然后,如果单击结果,您将能够查看所有跨度的追踪:

img

img

在此示例中,我们可以将跨度视为级联,因为当我们通过函数调用传递上下文时,我们生成了跨度层次结构。

现在您可以使用代码并进行修改,以便更深入了解跨度追踪。

正如您所见,使用可视化跟踪和跨度的工具将 OpenTelemetry 集成到我们的项目中并不复杂。

您可以使用我们示例中定义的代码开始配置您的真实服务,只需根据需要进行调整即可。正如您所见,这并不复杂,易于理解。

感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1269445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某60物联网安全之IoT漏洞利用实操2学习记录

物联网安全 文章目录 物联网安全IoT漏洞利用实操2(内存破坏漏洞)实验目的实验环境实验工具实验原理实验内容实验步骤ARM ROP构造与调试MIPS栈溢出漏洞逆向分析 IoT漏洞利用实操2(内存破坏漏洞) 实验目的 学会ARM栈溢出漏洞的原理…

如何使用 CSS columns 布局来实现自动分组布局?

最近在项目中碰到这样一个布局,有一个列表,先按照 4 2 的正常顺序排列,当超过 8 个后,会横向重新开始 4 2 的布局,有点像一个个独立的分组,然后水平排列,如下 图中序号是 dom 序列,所…

使用Java对yaml和properties互转,保证顺序、实测无BUG版本

使用Java对yaml和properties互转 一、 前言1.1 顺序错乱的原因1.2 遗漏子节点的原因 二、优化措施三、源码 一、 前言 浏览了一圈网上的版本,大多存在以下问题: 转换后顺序错乱遗漏子节点 基于此进行了优化,如果只是想直接转换&#xff0c…

IDEA2022 Git 回滚及回滚内容恢复

IDEA2022 Git 回滚 ①选择要回滚的地方,右键选择 ②选择要回滚的模式 ③开始回滚 ④soft模式回滚的内容会保留在暂存区 ⑤输入git push -f ,然后重新提交,即可同步远程 注意观察IDEA右下角分支的标记,蓝色代表远程内容未同步到本…

初识Java 18-5 泛型

目录 动态类型安全 异常 混型 C中的混型 替代方案 与接口混合 使用装饰器模式 与动态代理混合 本笔记参考自: 《On Java 中文版》 动态类型安全 在Java 5引入泛型前,老版本的Java程序中就已经存在了List等原生集合类型。这意味着,我们…

LeetCode(38)生命游戏【矩阵】【中等】

目录 1.题目2.答案3.提交结果截图 链接: 生命游戏 1.题目 根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板,每一个格子都可以看成是…

Linux(fork+exec创建进程)

1.进程创建 内核设计与实现43页; 执行了3次ps -f ,ps -f的父进程的ID(PPID)都是一样的,即bash. 实际上Linux上这个bash就是不断的复制自身,然后把复制出来的用exec替换成想要执行的程序(比如ps); 运行ps,发现ps是bash的一个子进程;原因就是bash把自己复制一份,然后替换成ps;…

深度学习-模型调试经验总结

1、 这句话的意思是:期望张量的后端处理是在cpu上,但是实际是在cuda上。排查代码发现,数据还在cpu上,但是模型已经转到cuda上,所以可以通过把数据转到cuda上解决。 解决代码: tensor.to("cuda")…

vuepress-----7、发布在GitHub

# 7、发布在GitHub 在你的项目中,创建一个如下的 deploy.sh 文件(请自行判断去掉高亮行的注释): #!/usr/bin/env sh# 确保脚本抛出遇到的错误 set -e# 生成静态文件 npm run docs:build# 进入生成的文件夹 cd docs/.vuepress/dist# 如果是发…

attention中Q,K,V的理解

第一种 1.首先定义三个线性变换矩阵,query,key,value: class BertSelfAttention(nn.Module):self.query nn.Linear(config.hidden_size, self.all_head_size) # 输入768, 输出768self.key nn.Linear(config.hidde…

python实验3 石头剪刀布游戏

实验3:石头剪刀布游戏 一、实验目的二、知识要点图三、实验1. 石头剪刀布2. 实现大侠个人信息 一、实验目的 了解3类基本组合数据类型。理解列表概念并掌握Python中列表的使用。理解字典概念并掌握Python中字典的使用。运用jieba库进行中文分词并进行文本词频统计。…

COGVLM论文解读(COGVLM:VISUAL EXPERT FOR LARGE LANGUAGE MODELS)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、摘要二、引言三、模型方法1、模型思路2、融合公式 四、训练方法总结 前言 2023年5月18日清华&智谱AI发布并开源VisualGLM-6B以来,清华KEG&…

竞赛选题 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分:4.2 损失函数:4.3 搭建seq2seq框架:4.4 测试部分:4.5 评价NLP测试效果:4.6 梯度截断…

BUUCTF [GXYCTF2019]BabyUpload 1详解(.htaccess配置文件特性)

题目环境:查看题目源码 SetHandler application/x-httpd-php 通过源码可以看出这道文件上传题目主要还是考察.htaccess配置文件的特性 倘若不先上传.htaccess配置文件,那么后台服务器就无法解析php代码 这个是需要注意的 .htaccess配置文件特性 概述来说…

函数学习 PTA 1使用函数输出一个整数的逆序数;3判断满足条件的三位数;5使用函数求余弦函数的近似值

其实一共有五道题,但那两道实在太过简单,也不好意思打出来给大家看,那么这篇博客,就让我一次性写三道题吧!也当是个小总结,睡前深思。 6-1 使用函数输出一个整数的逆序数 本题要求实现一个求整数的逆序数的…

STM32F407-14.3.6-01输入捕获模式

输入捕获模式 在输入捕获模式下,当相应的 ICx⑦ 信号检测到跳变沿后,将使用捕获/比较寄存器 (TIMx_CCRx⑪) 来锁存计数器的值。发生捕获事件时,会将相应的 CCXIF⑬ 标志(TIMx_SR 寄存器)置 1, 并可发送中断…

云时空社会化商业 ERP 系统 Shiro 反序列化漏洞复现

0x01 产品简介 时空云社会化商业ERP(简称时空云ERP) ,该产品采用JAVA语言和Oracle数据库, 融合用友软件的先进管理理念,汇集各医药企业特色管理需求,通过规范各个流通环节从而提高企业竞争力、降低人员成本…

SpringMVC多种类型数据响应

SpringMVC多种类型数据响应入门 1.概念 RequestMapping 作用:用于建立请求URL和处理请求方法之间的对应关系 位置: 类上,请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录 方法上,请求URL的第二级访问目…

【二叉树】Leetcode 199. 二叉树的右视图

力扣题目链接 解题思路 一开始&#xff0c;我以为只需要依次遍历最右边一列所有数即可&#xff0c;写出来的代码也通过了样例&#xff1a; class Solution { public:vector<int> rightSideView(TreeNode* root) {vector<int> ans;TreeNode* temp root;while(tem…

游戏缺少d3dx9_43.dll修复方法分享,快速解决dll缺失问题

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“找不到d3dx9_43.dll文件”。这个错误通常出现在运行某些游戏或应用程序时&#xff0c;d3dx9_43.dll是一个动态链接库文件&#xff0c;它是DirectX 9的一部分&#xff0c;用于支持游戏中的3…