Observability:使用 OpenTelemetry 手动检测 Go 应用程序

作者:Luca Wintergerst

DevOps 和 SRE 团队正在改变软件开发的流程。 DevOps 工程师专注于高效的软件应用程序和服务交付,而 SRE 团队是确保可靠性、可扩展性和性能的关键。 这些团队必须依赖全栈可观察性解决方案,使他们能够管理和监控系统,并确保问题在影响业务之前得到解决。

整个现代分布式应用程序堆栈的可观察性需要通常以仪表板的形式收集、处理和关联数据。 摄取所有系统数据需要跨堆栈、框架和提供程序安装代理,对于必须处理版本更改、兼容性问题和不随系统更改而扩展的专有代码的团队来说,这个过程可能具有挑战性且耗时。

得益于 OpenTelemetry (OTel),DevOps 和 SRE 团队现在拥有一种标准方法来收集和发送数据,该方法不依赖于专有代码,并且拥有大型社区支持,减少了供应商锁定。

在这篇博文中,我们将向你展示如何使用 OpenTelemetry 手动检测 Go 应用程序。 这种方法比使用自动检测稍微复杂一些

在之前的博客中,我们还回顾了如何使用 OpenTelemetry 演示并将其连接到 Elastic®,以及 Elastic 与 OpenTelemetry 的一些功能。 在本博客中,我们将使用另一个演示应用程序,它有助于以简单的方式突出显示手动检测。

最后,我们将讨论 Elastic 如何支持与 Elastic 和 OpenTelemetry 代理一起运行的混合模式应用程序。 这样做的好处是不需要 otel-collector! 此设置你你能够根据最适合你业务的时间表,缓慢而轻松地将应用程序迁移到使用 Elastic 的 OTel。

应用程序、先决条件和配置

我们在这个博客中使用的应用程序称为 Elastiflix,一个电影流应用程序。 它由多个用 .NET、NodeJS、Go 和 Python 编写的微服务组成。

在我们检测示例应用程序之前,我们首先需要了解 Elastic 如何接收遥测数据。

OpenTelemetry 的 Elastic 配置选项

Elastic Observability 的所有 APM 功能均可通过 OTel 数据使用。 其中一些包括:

  • Service maps
  • 服务详细信息(延迟、吞吐量、失败的 transactions)
  • 服务之间的依赖关系、分布式追踪
  • transactions(跟踪)
  • 机器学习 (ML) 相关性
  • Log 相关性

除了 Elastic 的 APM 和遥测数据的统一视图之外,你还可以使用 Elastic 强大的机器学习功能来减少分析,并发出警报以帮助降低 MTTR。

先决条件

  • Elastic Cloud 帐户 — 立即注册
  • 克隆 Elastiflix 演示应用程序,或你自己的 Go 应用程序
  • 对 Docker 的基本了解 — 可能安装 Docker Desktop
  • 对 Go 的基本了解

查看示例源代码

完整的源代码(包括本博客中使用的 Dockerfile)可以在 GitHub 上找到。 该存储库还包含相同的应用程序,但没有检测。 这使你可以比较每个文件并查看差异。

在开始之前,让我们先看一下未检测的代码。

这是我们可以接收 GET 请求的简单 go 应用程序。 请注意,此处显示的代码是稍微缩写的版本。

package mainimport ("log""net/http""os""time""github.com/go-redis/redis/v8""github.com/sirupsen/logrus""github.com/gin-gonic/gin""strconv""math/rand"
)var logger = &logrus.Logger{Out:   os.Stderr,Hooks: make(logrus.LevelHooks),Level: logrus.InfoLevel,Formatter: &logrus.JSONFormatter{FieldMap: logrus.FieldMap{logrus.FieldKeyTime:  "@timestamp",logrus.FieldKeyLevel: "log.level",logrus.FieldKeyMsg:   "message",logrus.FieldKeyFunc:  "function.name", // non-ECS},TimestampFormat: time.RFC3339Nano,},
}func main() {delayTime, _ := strconv.Atoi(os.Getenv("TOGGLE_SERVICE_DELAY"))redisHost := os.Getenv("REDIS_HOST")if redisHost == "" {redisHost = "localhost"}redisPort := os.Getenv("REDIS_PORT")if redisPort == "" {redisPort = "6379"}applicationPort := os.Getenv("APPLICATION_PORT")if applicationPort == "" {applicationPort = "5000"}// Initialize Redis clientrdb := redis.NewClient(&redis.Options{Addr:     redisHost + ":" + redisPort,Password: "",DB:       0,})// Initialize routerr := gin.New()r.Use(logrusMiddleware)r.GET("/favorites", func(c *gin.Context) {// artificial sleep for delayTimetime.Sleep(time.Duration(delayTime) * time.Millisecond)userID := c.Query("user_id")contextLogger(c).Infof("Getting favorites for user %q", userID)favorites, err := rdb.SMembers(c.Request.Context(), userID).Result()if err != nil {contextLogger(c).Error("Failed to get favorites for user %q", userID)c.String(http.StatusInternalServerError, "Failed to get favorites")return}contextLogger(c).Infof("User %q has favorites %q", userID, favorites)c.JSON(http.StatusOK, gin.H{"favorites": favorites,})})// Start serverlogger.Infof("App startup")log.Fatal(http.ListenAndServe(":"+applicationPort, r))logger.Infof("App stopped")
}

分步指南

步骤 0. 登录你的 Elastic Cloud 帐户

本博客假设你有 Elastic Cloud 帐户 - 如果没有,请按照说明开始使用 Elastic Cloud。

步骤 1. 安装并初始化 OpenTelemetry

第一步,我们需要向应用程序添加一些额外的包。

import ("github.com/go-redis/redis/extra/redisotel/v8""go.opentelemetry.io/otel""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/exporters/otlp/otlptrace""go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc""go.opentelemetry.io/otel/propagation""google.golang.org/grpc/credentials""crypto/tls"sdktrace "go.opentelemetry.io/otel/sdk/trace""go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin""go.opentelemetry.io/otel/trace""go.opentelemetry.io/otel/codes"
)

此代码导入必要的 OpenTelemetry 包,包括用于跟踪、导出和检测特定库(如 Redis)的包。

接下来我们读取 OTEL_EXPORTER_OTLP_ENDPOINT 变量并初始化导出器。

var (collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
)
var tracer trace.Tracerfunc initTracer() func(context.Context) error {tracer = otel.Tracer("go-favorite-otel-manual")// remove https:// from the collector URL if it existscollectorURL = strings.Replace(collectorURL, "https://", "", 1)secretToken := os.Getenv("ELASTIC_APM_SECRET_TOKEN")if secretToken == "" {log.Fatal("ELASTIC_APM_SECRET_TOKEN is required")}secureOption := otlptracegrpc.WithInsecure()exporter, err := otlptrace.New(context.Background(),otlptracegrpc.NewClient(secureOption,otlptracegrpc.WithEndpoint(collectorURL),otlptracegrpc.WithHeaders(map[string]string{"Authorization": "Bearer " + secretToken,}),otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})),),)if err != nil {log.Fatal(err)}otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithBatcher(exporter),),)otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{},propagation.TraceContext{},),)return exporter.Shutdown
}

为了检测到 Redis 的连接,我们将向其添加一个跟踪钩子(hook),为了检测 Gin,我们将添加 OTel 中间件。 这将自动捕获与我们的应用程序的所有交互,因为 Gin 将被完全检测。 此外,所有到 Redis 的传出连接也将被检测。

      // Initialize Redis clientrdb := redis.NewClient(&redis.Options{Addr:     redisHost + ":" + redisPort,Password: "",DB:       0,})rdb.AddHook(redisotel.NewTracingHook())// Initialize routerr := gin.New()r.Use(logrusMiddleware)r.Use(otelgin.Middleware("go-favorite-otel-manual"))

添加自定义 span

现在我们已经添加并初始化了所有内容,我们可以添加自定义跨度。

如果我们想为应用程序的一部分提供额外的检测,我们只需启动一个自定义 span,然后推迟结束该 span。


// start otel span
ctx := c.Request.Context()
ctx, span := tracer.Start(ctx, "add_favorite_movies")
defer span.End()

为了进行比较,这是我们示例应用程序的检测代码。 你可以在 GitHub 上找到完整的源代码。

package mainimport ("log""net/http""os""time""context""github.com/go-redis/redis/v8""github.com/go-redis/redis/extra/redisotel/v8""github.com/sirupsen/logrus""github.com/gin-gonic/gin""go.opentelemetry.io/otel""go.opentelemetry.io/otel/attribute""go.opentelemetry.io/otel/exporters/otlp/otlptrace""go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc""go.opentelemetry.io/otel/propagation""google.golang.org/grpc/credentials""crypto/tls"sdktrace "go.opentelemetry.io/otel/sdk/trace""go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin""go.opentelemetry.io/otel/trace""strings""strconv""math/rand""go.opentelemetry.io/otel/codes")var tracer trace.Tracerfunc initTracer() func(context.Context) error {tracer = otel.Tracer("go-favorite-otel-manual")collectorURL = strings.Replace(collectorURL, "https://", "", 1)secureOption := otlptracegrpc.WithInsecure()// split otlpHeaders by comma and convert to mapheaders := make(map[string]string)for _, header := range strings.Split(otlpHeaders, ",") {headerParts := strings.Split(header, "=")if len(headerParts) == 2 {headers[headerParts[0]] = headerParts[1]}}exporter, err := otlptrace.New(context.Background(),otlptracegrpc.NewClient(secureOption,otlptracegrpc.WithEndpoint(collectorURL),otlptracegrpc.WithHeaders(headers),otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{})),),)if err != nil {log.Fatal(err)}otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithBatcher(exporter),//sdktrace.WithResource(resources),),)otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{},propagation.TraceContext{},),)return exporter.Shutdown
}var (collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")otlpHeaders = os.Getenv("OTEL_EXPORTER_OTLP_HEADERS")
)var logger = &logrus.Logger{Out:   os.Stderr,Hooks: make(logrus.LevelHooks),Level: logrus.InfoLevel,Formatter: &logrus.JSONFormatter{FieldMap: logrus.FieldMap{logrus.FieldKeyTime:  "@timestamp",logrus.FieldKeyLevel: "log.level",logrus.FieldKeyMsg:   "message",logrus.FieldKeyFunc:  "function.name", // non-ECS},TimestampFormat: time.RFC3339Nano,},
}func main() {cleanup := initTracer()defer cleanup(context.Background())redisHost := os.Getenv("REDIS_HOST")if redisHost == "" {redisHost = "localhost"}redisPort := os.Getenv("REDIS_PORT")if redisPort == "" {redisPort = "6379"}applicationPort := os.Getenv("APPLICATION_PORT")if applicationPort == "" {applicationPort = "5000"}// Initialize Redis clientrdb := redis.NewClient(&redis.Options{Addr:     redisHost + ":" + redisPort,Password: "",DB:       0,})rdb.AddHook(redisotel.NewTracingHook())// Initialize routerr := gin.New()r.Use(logrusMiddleware)r.Use(otelgin.Middleware("go-favorite-otel-manual"))// Define routesr.GET("/", func(c *gin.Context) {contextLogger(c).Infof("Main request successful")c.String(http.StatusOK, "Hello World!")})r.GET("/favorites", func(c *gin.Context) {// artificial sleep for delayTimetime.Sleep(time.Duration(delayTime) * time.Millisecond)userID := c.Query("user_id")contextLogger(c).Infof("Getting favorites for user %q", userID)favorites, err := rdb.SMembers(c.Request.Context(), userID).Result()if err != nil {contextLogger(c).Error("Failed to get favorites for user %q", userID)c.String(http.StatusInternalServerError, "Failed to get favorites")return}contextLogger(c).Infof("User %q has favorites %q", userID, favorites)c.JSON(http.StatusOK, gin.H{"favorites": favorites,})})// Start serverlogger.Infof("App startup")log.Fatal(http.ListenAndServe(":"+applicationPort, r))logger.Infof("App stopped")
}

步骤 2. 使用环境变量运行 Docker 镜像

正如 OTEL 文档中所指定的,我们将使用环境变量并传入 APM 代理配置部分中找到的配置值。

由于 Elastic 本身接受 OTLP,因此我们只需要提供 OTEL Exporter 需要发送数据的端点和身份验证,以及一些其他环境变量。

在 Elastic Cloud 和 Kibana® 中从哪里获取这些变量

你可以从路径 /app/home#/tutorial/apm 下的 Kibana 复制端点和 token。

你需要复制 OTEL_EXPORTER_OTLP_ENDPOINT 以及 OTEL_EXPORTER_OTLP_HEADERS。

构建镜像

docker build -t  go-otel-manual-image .

运行镜像

docker run \-e OTEL_EXPORTER_OTLP_ENDPOINT="<REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT>" \-e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <REPLACE WITH TOKEN>" \-e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production,service.name=go-favorite-otel-manual" \-p 5000:5000 \go-otel-manual-image

你现在可以发出一些请求来生成跟踪数据。 请注意,这些请求预计会返回错误,因为此服务依赖于你当前未运行的 Redis 连接。 如前所述,你可以在此处找到使用 Docker compose 的更完整示例。

curl localhost:500/favorites
# or alternatively issue a request every secondwhile true; do curl "localhost:5000/favorites"; sleep 1; done;

跟踪如何显示在 Elastic 中?

现在该服务已完成检测,在查看 Node.js 服务的 transaction 部分时,你应该会在 Elastic APM 中看到以下输出:

结论

在这篇博客中,我们讨论了以下内容:

  • 如何使用 OpenTelemetry 手动检测 Go
  • 如何正确初始化 OpenTelemetry 并添加自定义范围
  • 如何使用 Elastic 轻松设置 OTLP ENDPOINT 和 OTLP HEADERS,而不需要收集器

希望它能够提供一个易于理解的使用 OpenTelemetry 检测 Go 的演练,以及将跟踪发送到 Elastic 是多么容易。

有关 OpenTelemetry with Elastic 的其他资源:

开发者资源:

  • Elastiflix application, a guide to instrument different languages with OpenTelemetry
  • Python: Auto-instrumentation, Manual-instrumentation
  • Java: Auto-instrumentation, Manual-instrumentation
  • Node.js: Auto-instrumentation, Manual-instrumentation
  • .NET: Auto-instrumentation, Manual-instrumentation
  • Go: Manual-instrumentation
  • Best practices for instrumenting OpenTelemetry


常规配置和用例资源:

  • Independence with OpenTelemetry on Elastic
  • Modern observability and security on Kubernetes with Elastic and OpenTelemetry
  • 3 models for logging with OpenTelemetry and Elastic
  • Adding free and open Elastic APM as part of your Elastic Observability deployment
  • Capturing custom metrics through OpenTelemetry API in code with Elastic
  • Future-proof your observability platform with OpenTelemetry and Elastic
  • Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more

原文:Manual instrumentation of Go applications with OpenTelemetry | Elastic Blog

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统IO和标准IO

一.系统IO 系统 I/O&#xff08;Input/Output&#xff09;是计算机操作系统提供给应用程序的一种输入和输出方式。它通过系统调用&#xff08;系统内核提供的函数&#xff09;来实现数据的读取和写入。系统 I/O 可以用于与文件、设备&#xff08;例如磁盘驱动器、网络接口、串…

使用vite创建vue3项目及项目的配置 | 环境准备 ESLint配置 prettier配置 husky配置 项目继承

文章目录 使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置 - js代码检测工具1.安装ESLint到开发环境 devDependencies2.生成配置文件:.eslint.cjs**3.安装vue3环境代码校验插件**4. 修改.eslintrc.cjs配置文件5.生成ESLint忽略文件6.在package.js…

[BJDCTF2020]Mark loves cat foreach导致变量覆盖

这里我们着重了解一下变量覆盖 首先我们要知道函数是什么 foreach foreach (iterable_expression as $value)statement foreach (iterable_expression as $key > $value)statement第一种格式遍历给定的 iterable_expression 迭代器。每次循环中&#xff0c;当前单元的值被…

使用 Nginx 实现企业微信域名配置中的校验文件跳转

背景 在企业微信中配置业务域名时&#xff0c;通常需要在该域名的根路径下放置一个校验文件&#xff0c;以验证域名的所有权。然而&#xff0c;如果该域名是第三方的&#xff0c;你可能无法直接在根路径下放置文件。在这种情况下&#xff0c;你可以使用 Nginx 来实现校验文件的…

OPC DCOM快速配置

目录 1 老系统配置 1.1 移除Windows 安全 1.2 建立相互能识别的用户账号 1.3 配置系统宽泛的DCOM设置 1.4 配置Server的特殊DCOM设置 1.5 恢复Windows安全 1 老系统配置 远程OPC访问必须在服务器和客户端两端配置DCOM。本文讲述如何正确配置 DCOM 的步骤并保证安全。 新…

Git(6)——GitHub

目录 一、简介 二、概要 三、注册 ​四、创建仓库 五、推送本地代码 六、拉取远端代码 一、简介 在Git&#xff08;5&#xff09;中&#xff0c;我们已经对Git分支的概念和用法有了一定了解&#xff0c;对于在本地进行代码版本管理&#xff0c;其实当前所学的东西基本已经…

CocosCreator3.8研究笔记(十八)CocosCreator UI组件(二)

前面的文章已经介绍了Canvas 组件、UITransform 组件、Widget 组件 。 想了解的朋友&#xff0c;请查看 CocosCreator3.8研究笔记&#xff08;十七&#xff09;CocosCreator UI组件&#xff08;一&#xff09;。 今天我们主要介绍CocosCreator 常用容器组件&#xff1a;Layout …

【深度学习实验】线性模型(四):使用Pytorch实现线性模型:使用随机梯度下降优化器训练模型

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入库 1. 线性模型linear_model 2. 损失函数loss_function 3. 定义数据 4. 初始化权重和偏置 5. 模型训练 6. 迭代 7. 实验结果 8. 完整代码 一、实验介绍 使用随机梯度下降优化…

解决Permission is not allowed后基于Ubuntu23.04安装配置docker与docker-compose

参考&#xff1a;Docker官网-Install Docker Engine on Ubuntu 一、 Install using the Apt repository 1.1 Set up Docker’s Apt repository 1.1.1 Add Docker’s official GPG key # Add Dockers official GPG key: sudo apt-get updatesudo apt-get install ca-certifi…

Java文字描边效果实现

效果&#xff1a; FontUtil工具类的完整代码如下&#xff1a; 其中实现描边效果的函数为&#xff1a;generateAdaptiveStrokeFontImage() package com.ncarzone.data.contentcenter.biz.img.util;import org.springframework.core.io.ClassPathResource; import org.springfr…

ApplicationContext版本的快速入门

ApplicationContext快速入门 ApplicationContext称为Spring容器&#xff0c;内部封装了BeanFactory&#xff0c;比BeanFactory功能更加丰富和强大&#xff0c;使用ApplicationContext进行开发时&#xff0c;xml配置文件的名称习惯写成applicationContext.xml。 BeanFactory和…

Python语言学习实战-内置函数sorted()的使用(附源码和实现效果)

实现功能 sorted()函数是Python的内置函数之一&#xff0c;用于对可迭代对象进行排序操作。它可以对列表、元组、字符串等可迭代对象进行排序&#xff0c;并返回一个新的已排序的列表。 sorted()函数的语法如下&#xff1a; sorted(iterable, keyNone, reverseFalse)其中&am…

6-2 pytorch中训练模型的3种方法

Pytorch通常需要用户编写自定义训练循环&#xff0c;训练循环的代码风格因人而异。&#xff08;养成自己的习惯&#xff09; 有3类典型的训练循环代码风格&#xff1a;脚本形式训练循环&#xff0c;函数形式训练循环&#xff0c;类形式训练循环。 下面以minist数据集的多分类模…

ROS 入门

目录 简介 ROS诞生背景 ROS的设计目标 ROS与ROS2 安装ROS 1.配置ubuntu的软件和更新 2.设置安装源 3.设置key 4.安装 5.配置环境变量 安装可能出现的问题 安装构建依赖 卸载 ROS架构 1.设计者 2.维护者 3. 立足系统架构: ROS 可以划分为三层 ROS通信机制 话…

【窗体】Winform两个窗体之间通过委托事件进行值传递,基础篇

2023年&#xff0c;第38周。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; 在实际项目中&#xff0c;我们可能会用到一些窗体做一些小工具或者小功能。比如&#xff1a;运行程序&#xff0c;在主窗体A基础上&#xff0c;点击某个按钮希望能…

移动设备管理(MDM):密码管理

密码是企业设备的第一道防线&#xff0c;它们通过限制锁屏之外的未经授权访问来确保设备中包含的敏感数据的安全性和机密性。对于组织&#xff0c;强烈建议遵循复杂的密码策略&#xff0c;因为简单和通用的密码使入侵者能够毫不费力地访问敏感的企业数据。密码越简单&#xff0…

ElasticSearch 5.6.3 自定义封装API接口

在实际业务中&#xff0c;查询 elasticsearch 时会遇到很多特殊查询&#xff0c;官方接口包有时不便利&#xff0c;特殊情况需要自定义接口&#xff0c;所以为了灵活使用、维护更新 编写了一套API接口&#xff0c;仅供学习使用 当前自定义API接口依赖 elasticsearch 5.6.3 版本…

l8-d21 域名解析与http服务器实现原理

一、域名解析gethostbyname函数 主机结构在 <netdb.h> 中定义如下&#xff1a; struct hostent { char *h_name; /* 官方域名 */ char **h_aliases; /* 别名*/ int h_addrtype; /* 地址族&#xff08;地址类型&#xff09; */ int h_l…

Scotch: Combining SGX and SMM to Monitor Cloud Resource Usage【TEE的应用】

目录 摘要引言贡献 背景SMMXen Credit Scheduler与资源核算SGX 威胁模型Scheduler attacksResource interference attacksVM Escape attacks 架构Resource Accounting WorkflowCost of Accounting 具体的部署和评估见论文相关工作Resource Accounting基于SMM的方法基于SGX的系统…

MySQL 几种导数据的方法与遇到的问题

零、说在前面 MySQL导数据通常使用第三方工具和MySQL自身的工具&#xff0c;本文分别就这两类方法分别介绍。 一、第三方工具之 Navicat 1.1、Navicat的“数据传输”工具 打开Navicat&#xff0c;点击“工具”标签&#xff0c;找到“数据传输”&#xff0c;即可看到操作界面。…