概述

在上文中,我们已经了解了 Go 语言中 OpenTelemetry 相关的使用方式。
但是,我们并没有提及太多自动插桩库等相关的使用。
在本文中,我们将会讲解一些框架或Lib库对应的自动插桩库及其相关的一些使用。

Mongo

首先,我们来讲解一下通过 Go 语言操作 MongoDB 时,如何对 MongoDB Client 进行插桩。
目前,Go 操作 MongoDB 主流的客户端库是 go.mongodb.org/mongo-driver 。
当然,也有一些更高层次封装的 Lib 库,例如 github.com/qiniu/qmgo 。其中,qmgo 是基于 mongo-driver 开发的,因此,mongo-driver 的插桩方式在 qmgo 中也适用。
下面,我们通过一个示例演示一下在 qmgo 中如何使用 Otel 的 MongoDB 插桩库进行插桩:
首先,我们需要安装相关依赖:

  1. go get go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo

然后,示例代码如下:

  1. import (
  2. "github.com/qiniu/qmgo"
  3. qmongoOpt "github.com/qiniu/qmgo/options"
  4. mongoOpts "go.mongodb.org/mongo-driver/mongo/options"
  5. "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
  6. )
  7. func main() {
  8. // 创建一个官方 MongoDB 的 ClientOps
  9. opts := mongoOpts.Client()
  10. // 设置 opts 中的 Monitor 属性,使用 otelmongo.NewMonitor() 进行插桩
  11. opts.Monitor = otelmongo.NewMonitor()
  12. // 将官方 MongoDB 的 ClientOps 转化为 qmgo 中的 ClientOps 对象
  13. option := qmongoOpt.ClientOptions{
  14. ClientOptions: opts,
  15. }
  16. // 使用 qmgo 中的 ClientOps 对象来创建一个 qmgo Client
  17. client, err := qmgo.Open(ctx, &qmgo.Config{
  18. Uri: mongoSrv,
  19. Database: options.DbName,
  20. MaxPoolSize: &maxNumber,
  21. MinPoolSize: &minNumber,
  22. }, option)
  23. // 此后,所有使用该 client 进行的 MongoDB 操作都会自动进行插桩相关的操作和数据采集
  24. // ...
  25. }

从上述代码和描述中可以看到,Go 语言中 MongoDB 的插桩库是非常简单易用的。只需要在 MongoDB Client 的 Option 对象中设置 Monitor 属性为 otelmongo.NewMonitor() 即可完成自动插桩的过程。

Gin

Gin 是 Go 语言中非常流行的一个 Web 框架。
下面,我们来详细讲解一下在 Gin Web 服务中,如何实现 OpenTelemetry 的自动插桩。
Gin 框架提供了 Middleware 的机制来实现 AOP 编程,而 Gin 的 Otel 插桩库正是通过这一机制来实现了一个 Middleware 从而提供针对每个请求进行插桩的功能的。
首先,我们来安装相关的依赖:

  1. go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin

下面,我们通过示例代码来演示一下相关的使用:

  1. import (
  2. "github.com/gin-gonic/gin"
  3. "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
  4. )
  5. func main() {
  6. r := gin.Default()
  7. r.use(otelgin.Middleware("demo")) // 引入 gin otel 插桩库
  8. r.GET("/ping", func(c *gin.Context) {
  9. // 在 Context 传递过程中,需要使用 c.Request.Context() 作为 context 向下传递
  10. ctx, span = otel.Tracer("demo").Start(c.Request.Context(), "sleep")
  11. time.Sleep(1 * time.Second)
  12. span.End()
  13. c.JSON(200, gin.H{
  14. "message": "pong",
  15. })
  16. })
  17. r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
  18. }

关于 Gin 框架的 Otel 插桩库需要主要需要注入如下两点:

  1. 通过 Middleware 的机制进行插桩,例如 r.use(otelgin.Middleware("demo"))
  2. 获取请求对应的包含当前 span 的 context 对象时,不能直接使用 GET、POST 等方法中传入的变量,而应该是 c.Request.Context() 返回的对象,只要该对象才包含了对应当前请求 span 的上下文信息。

logrus

logrus 是一个 Go 中流行的日志库。你可能会好奇日志库也需要插桩么?这其实就需要回到 Otel 本身的定位上面了。OpenTelemetry 的定位和 OpenTracing 不同,相比 OpenTracing 仅关注的 Trace 信息的管理而言,OpenTelemetry 希望是同时进行 Trace、Metric 和 Log 的统一管理。因此,针对 Log 的管理而言,Logrus 的插桩库就格外重要了。
同时,针对 logrus 插桩后,在打印日志的过程中,如果传入了对应的 context 且该 context 本身包含 span 信息的话,则 otel 会将该日志信息当做 Events 事件来添加到对应的 span 上。
下面,我们来看一下如何针对 logrus 来实现自动插桩吧:

  1. import (
  2. "github.com/sirupsen/logrus",
  3. "github.com/uptrace/opentelemetry-go-extra/otellogrus"
  4. )
  5. func main() {
  6. // 通过 AddHook 函数来实现指定级别的日志自动插桩
  7. logrus.AddHook(otellogrus.NewHook(otellogrus.WithLevels(
  8. logrus.PanicLevel,
  9. logrus.FatalLevel,
  10. logrus.ErrorLevel,
  11. logrus.WarnLevel,
  12. logrus.InfoLevel,
  13. )))
  14. // 需要传入对应包含 span 的 context 对象
  15. ctx, span = otel.Tracer("demo").Start(c.Request.Context(), "sleep")
  16. logrus.WithContext(ctx).Infof("hello world!")
  17. span.End()
  18. }

从上述代码中,关于 logrus 的自动插桩库的使用需要关注如下两点:

  • 通过 logrus.AddHook 函数来实现自动插桩,插桩时还可以指定插桩生效的日志级别;
  • 在打印日志时,需要对 logrus 调用 WithContext 函数来传入对应的 context 上下文后,对应的插桩库才能生效。