Go Web 应用的 instrumentation
博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。
在这篇博文中,您将亲手学习如何在没有任何先验知识的情况下,使用 OpenTelemetry Go 创建和可视化跟踪。
我们将从创建一个使用 Mongo 和 Gin 框架的简单待办事项应用开始。然后,我们将把跟踪数据发送到 Jaeger Tracing 进行可视化。您可以在这个 GitHub 仓库 中找到所有相关文件。

Hello world: OpenTelemetry Go 示例
我们将从创建我们的待办事项服务开始,并安装两个库(Gin 和 Mongo)来理解 instrumentation 的工作原理。
步骤 1:为我们的待办事项应用程序创建 main.go 文件
安装 Gin 和 Mongo-driver
go get -u github.com/gin-gonic/gin go get go.mongodb.org/mongo-driver/mongo设置 gin 和 mongo 以监听“/todo”
创建一些待办事项来填充 Mongo
package main import ( "context" "net/http" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) var client * mongo.Client func main() { connectMongo() setupWebServer() } func connectMongo() { opts: = options.Client() opts.ApplyURI("mongodb://:27017") client, _ = mongo.Connect(context.Background(), opts) //Seed the database with todo's docs: = [] interface {} { bson.D { { "id", "1" }, { "title", "Buy groceries" } }, bson.D { { "id", "2" }, { "title", "install Aspecto.io" } }, bson.D { { "id", "3" }, { "title", "Buy dogz.io domain" } }, } client.Database("todo").Collection("todos").InsertMany(context.Background(), docs) } func setupWebServer() { r: = gin.Default() r.GET("/todo", func(c * gin.Context) { collection: = client.Database("todo").Collection("todos") //Important: Make sure to pass c.Request.Context() as the context and not c itself - TBD cur, findErr: = collection.Find(c.Request.Context(), bson.D {}) if findErr != nil { c.AbortWithError(500, findErr) return } results: = make([] interface {}, 0) curErr: = cur.All(c, & results) if curErr != nil { c.AbortWithError(500, curErr) return } c.JSON(http.StatusOK, results) }) _ = r.Run(":8080") }
现在我们的待办事项小应用已经准备好了,让我们引入 OpenTelemetry。
步骤 2:安装 OpenTelemetry Go
我们将配置 OpenTelemetry 来 instrumentation 我们的 Go 应用。
要安装 OTel SDK,请运行
go get go.opentelemetry.io/otel / go.opentelemetry.io/otel/sdk /instrumentation 我们的 Gin 和 Mongo 库以生成跟踪。
Gin & Mongo instrumentation: 安装 otelgin & otelmongo
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin / go get go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo
Gin instrumentation: gin.Context
我们之前讨论过上下文传播的概念——即在分布式服务之间传递元数据以关联我们系统中事件的方式。
Gin 框架有自己的类型 gin.Context,它作为参数传递给 HTTP 处理程序。但是,应该向下传递给 mongo 操作的上下文是标准 Go 库 Context 对象,它可以在 gin.Context.Request.Context 中找到。
//Make sure to pass c.Request.Context() as the context and not c itself
cur, findErr := collection.Find(c.Request.Context(), bson.D{})
因此,请确保将 Context 传递给 MongoDB 操作。有关更多信息,请查看此 [issue](https://github.com/gin-gonic/gin/issues/1335)。
现在我们的待办事项应用已经准备好并且进行了 instrumentation。是时候充分利用 OpenTelemetry 了。我们可视化跟踪的能力正是这项技术真正的故障排除力量所在。
为了可视化,我们将使用开源的 Jaeger Tracing。
使用 Jaeger 进行可视化
OpenTelemetry Go 和 Jaeger Tracing:将跟踪导出到 Jaeger
[Jaeger Tracing](https://www.aspecto.io/blog/jaeger-tracing-the-ultimate-guide/) 是一个开源项目套件,负责管理整个分布式跟踪“堆栈”:客户端、收集器和 UI。Jaeger UI 是最常用的开源工具,用于可视化跟踪。
设置如下所示
安装 Jaeger exporter
go get go.opentelemetry.io/otel/exporters/jaeger创建一个 tracing 文件夹和一个 jaeger.go 文件
将以下代码添加到文件中
package tracing import ( "go.opentelemetry.io/otel/exporters/jaeger" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) func JaegerTracerProvider()(*sdktrace.TracerProvider, error) { exp, err: = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("https://:14268/api/traces"))) if err != nil { return nil, err } tp: = sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("todo-service"), semconv.DeploymentEnvironmentKey.String("production"), )), ) return tp, nil }回到 main.go 文件,修改我们的代码以使用我们刚刚创建的 JaegerTracerProvider 函数
func main() { tp, tpErr: = tracing.JaegerTracerProvider() if tpErr != nil { log.Fatal(tpErr) } otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext {}, propagation.Baggage {})) connectMongo() setupWebServer() }接下来,我们将连接我们安装的 instrumentations。
添加 Mongo instrumentation。在我们的 connectMongo 函数中,通过添加这一行
opts.Monitor = otelmongo.NewMonitor()函数应该如下所示
func connectMongo() { opts: = options.Client() //Mongo OpenTelemetry instrumentation opts.Monitor = otelmongo.NewMonitor() opts.ApplyURI("mongodb://:27017") client, _ = mongo.Connect(context.Background(), opts) //Seed the database with some todo's docs: = [] interface {} { bson.D { { "id", "1" }, { "title", "Buy groceries" } }, bson.D { { "id", "2" }, { "title", "install Aspecto.io" } }, bson.D { { "id", "3" }, { "title", "Buy dogz.io domain" } }, } client.Database("todo").Collection("todos").InsertMany(context.Background(), docs) }现在,添加 Gin instrumentation。
转到 startWebServer 函数,在创建 gin 实例后立即添加这一行
r.Use(otelgin.Middleware("todo-service"))函数应该如下所示
func startWebServer() { r: = gin.Default() //Gin OpenTelemetry instrumentation r.Use(otelgin.Middleware("todo-service")) r.GET("/todo", func(c * gin.Context) { collection: = client.Database("todo").Collection("todos") //make sure to pass c.Request.Context() as the context and not c itself cur, findErr: = collection.Find(c.Request.Context(), bson.D {}) if findErr != nil { c.AbortWithError(500, findErr) return } results: = make([] interface {}, 0) curErr: = cur.All(c, & results) if curErr != nil { c.AbortWithError(500, curErr) return } c.JSON(http.StatusOK, results) }) _ = r.Run(":8080") }有关完整的 `main.go` 文件,请参阅下文。现在我们终于可以导出到 Jaeger 了。
package main import ( "context" "log" "net/http" "github.com/aspecto-io/opentelemetry-examples/tracing" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) var client * mongo.Client func main() { //Export traces to Jaeger tp, tpErr: = tracing.JaegerTracerProvider() if tpErr != nil { log.Fatal(tpErr) } otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext {}, propagation.Baggage {})) connectMongo() startWebServer() } func connectMongo() { opts: = options.Client() //Mongo OpenTelemetry instrumentation opts.Monitor = otelmongo.NewMonitor() opts.ApplyURI("mongodb://:27017") client, _ = mongo.Connect(context.Background(), opts) //Seed the database with some todo's docs: = [] interface {} { bson.D { { "id", "1" }, { "title", "Buy groceries" } }, bson.D { { "id", "2" }, { "title", "install Aspecto.io" } }, bson.D { { "id", "3" }, { "title", "Buy dogz.io domain" } }, } client.Database("todo").Collection("todos").InsertMany(context.Background(), docs) } func startWebServer() { r: = gin.Default() //gin OpenTelemetry instrumentation r.Use(otelgin.Middleware("todo-service")) r.GET("/todo", func(c * gin.Context) { collection: = client.Database("todo").Collection("todos") //Make sure to pass c.Request.Context() as the context and not c itself cur, findErr: = collection.Find(c.Request.Context(), bson.D {}) if findErr != nil { c.AbortWithError(500, findErr) return } results: = make([] interface {}, 0) curErr: = cur.All(c, & results) if curErr != nil { c.AbortWithError(500, curErr) return } c.JSON(http.StatusOK, results) }) _ = r.Run(":8080") }
导出跟踪到 Jaeger
- 使用 `go run main.go` 运行 todo-service。
- 要生成一些跟踪,请向 https://:8080/todo 发送 HTTP GET 请求。
- 要查看跟踪,请在 https://:16686/search 打开 Jaeger。
您现在可以看到 Jaeger UI。选择 todo-service 并点击 Find traces。您应该在右侧看到您的跟踪。

Jaeger UI 显示了我们的 todo-service 的 OpenTelemetry Go 跟踪。通过点击跟踪,您可以深入了解更多关于它的详细信息,从而使您能够进一步自行调查。

摘要
这就是全部内容!希望本指南内容丰富且易于遵循。您可以在我们的 GitHub 仓库 中找到所有可用的文件。
本文的一个版本 最初发布 在 Aspecto 博客上。