Go Web-app Instrumentation

在本博文中,您将亲自学习如何在没有先前知识的情况下使用OpenTelemetry Go创建和可视化跟踪。

我们将首先创建一个使用Mongo和Gin框架的简单待办事项应用程序。然后,我们将把跟踪数据发送到Jaeger Tracing以进行可视化。您可以在这个GitHub存储库中找到所有相关的文件。

OpenTelemetry Go - The Mandalorian

Hello world: OpenTelemetry Go示例

我们将从创建待办事项服务并安装两个库(Gin和Mongo)开始,以了解如何进行工具化。

第1步:为我们的待办事项应用程序创建main.go文件

  1. 安装Gin和Mongo-driver

    go get -u github.com/gin-gonic/gin
    go get go.mongodb.org/mongo-driver/mongo
    
  2. 设置gin和mongo监听“/todo”

  3. 创建一些用于种子数据的待办事项

    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://localhost:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //用待办事项种子数据填充数据库
        docs := []interface{}{
            bson.D{
                {"id", "1"},
                {"title", "购买杂货"},
            },
            bson.D{
                {"id", "2"},
                {"title", "安装Aspecto.io"},
            },
            bson.D{
                {"id", "3"},
                {"title", "购买dogz.io域名"},
            },
        }
        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")
            //重要提示:确保将c.Request.Context()作为上下文传递,而不是c本身-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以工具化我们的Go应用程序。

  1. 要安装OTel SDK,请运行:

    go get go.opentelemetry.io/otel/
    go.opentelemetry.io/otel/sdk/
    
  2. 工具化我们的Gin和Mongo库以生成跟踪。

  3. Gin和Mongo工具化:安装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工具化:gin.Context

我们之前讨论了上下文传播的概念 - 在分布式服务之间传递元数据以关联系统中的事件的方法。

Gin框架有自己的gin.Context类型,作为参数传递给HTTP处理程序。但是,应传递到Mongo操作的上下文实际上是标准的Go库Context对象,该对象在gin.Context.Request.Context中可用。

//确保将c.Request.Context()作为上下文传递,而不是c本身
cur, findErr := collection.Find(c.Request.Context(), bson.D{})

因此,请确保将上下文传递给MongoDB操作。欲了解更多信息,请参考此问题。

现在,我们的待办事项应用程序已准备就绪并进行了工具化。现在该利用OpenTelemetry的全部潜力了。跟踪可视化是这项技术真正的故障排除能力体现之处。

为了进行可视化,我们将使用开源的Jaeger Tracing。

使用Jaeger进行可视化

OpenTelemetry Go和Jaeger Tracing:将跟踪导出到Jaeger

Jaeger Tracing是一套管理整个分布式跟踪“栈”的开源项目套件:客户端、收集器和UI。Jaeger UI是最常用的开源工具,用于可视化跟踪。

设置如下:

  1. 安装Jaeger导出器

    go get go.opentelemetry.io/otel/exporters/jaeger
    
  2. 创建一个tracing文件夹和一个jaeger.go文件

  3. 将以下代码添加到文件中

    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 JaegerTraceProvider() (*sdktrace.TracerProvider, error) {
        exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost: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
    }
    
  4. 返回到main.go文件并修改我们的代码以使用刚刚创建的JaegerTraceProvider函数

    func main() {
        tp, tpErr := tracing.JaegerTraceProvider()
        if tpErr != nil {
            log.Fatal(tpErr)
        }
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
        connectMongo()
        setupWebServer()
    }
    

    接下来,我们将连接已安装的工具。

  5. 添加Mongo工具化。在connectMongo函数中添加以下行

    opts.Monitor = otelmongo.NewMonitor()
    

    函数应如下所示:

    func connectMongo() {
        opts := options.Client()
        //Mongo OpenTelemetry工具化
        opts.Monitor = otelmongo.NewMonitor()
        opts.ApplyURI("mongodb://localhost:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //用一些待办事项填充数据库
        docs := []interface{}{
            bson.D{
                {"id", "1"},
                {"title", "购买杂货"},
            },
            bson.D{
                {"id", "2"},
                {"title", "安装Aspecto.io"},
            },
            bson.D{
                {"id", "3"},
                {"title", "购买dogz.io域名"},
            },
        }
        client.Database("todo").Collection("todos").InsertMany(context.Background(), docs)
    }
    

    现在,添加Gin工具化。

  6. 转到startWebServer函数并在创建gin实例后添加以下行

    r.Use(otelgin.Middleware("todo-service"))
    

    函数应如下所示

    func startWebServer() {
        r := gin.Default()
        //Gin OpenTelemetry工具化
        r.Use(otelgin.Middleware("todo-service"))
        r.GET("/todo", func(c *gin.Context) {
            collection := client.Database("todo").Collection("todos")
            //确保将c.Request.Context()作为上下文传递,而不是c本身
            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() {
        //将跟踪导出到Jaeger
        tp, tpErr := tracing.JaegerTraceProvider()
        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工具化
        opts.Monitor = otelmongo.NewMonitor()
        opts.ApplyURI("mongodb://localhost:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //用一些待办事项填充数据库
        docs := []interface{}{
            bson.D{
                {"id", "1"},
                {"title", "购买杂货"},
            },
            bson.D{
                {"id", "2"},
                {"title", "安装Aspecto.io"},
            },
            bson.D{
                {"id", "3"},
                {"title", "购买dogz.io域名"},
            },
        }
        client.Database("todo").Collection("todos").InsertMany(context.Background(), docs)
    }
    
    func startWebServer() {
        r := gin.Default()
        //gin OpenTelemetry工具化
        r.Use(otelgin.Middleware("todo-service"))
        r.GET("/todo", func(c *gin.Context) {
            collection := client.Database("todo").Collection("todos")
            //确保将c.Request.Context()作为上下文传递,而不是c本身
            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

  1. 运行go run main.go以启动todo-service。
  2. 为了生成一些跟踪,请向http://localhost:8080/todo发出HTTP GET请求。
  3. 要查看跟踪,请打开Jaeger网址http://localhost:16686/search

现在,您可以看到Jaeger UI了。选择todo-service,然后点击“Find traces”。您应该可以在右侧看到您的跟踪:

Jaeger UI显示我们的todo-service中使用OpenTelemetry的Go跟踪

Jaeger UI显示我们的todo-service中使用OpenTelemetry的Go跟踪 通过单击跟踪,您可以深入了解并查看更多细节,以便进一步自行调查:

Jaeger UI。待办事项服务的详细信息

Summary

就是这样!我们希望本指南易于理解和跟随。您可以在我们的GitHub 存储库中找到所有准备好使用的文件。

本文 [的版本原先发布在 ][] Aspecto博客上。

[的版本原先发布在 ][] https://www.aspecto.io/blog/opentelemetry-go-getting-started/