概述
在上文中,我们已经了解了 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 插桩库进行插桩:
首先,我们需要安装相关依赖:
go get go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo
然后,示例代码如下:
import (
"github.com/qiniu/qmgo"
qmongoOpt "github.com/qiniu/qmgo/options"
mongoOpts "go.mongodb.org/mongo-driver/mongo/options"
"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
)
func main() {
// 创建一个官方 MongoDB 的 ClientOps
opts := mongoOpts.Client()
// 设置 opts 中的 Monitor 属性,使用 otelmongo.NewMonitor() 进行插桩
opts.Monitor = otelmongo.NewMonitor()
// 将官方 MongoDB 的 ClientOps 转化为 qmgo 中的 ClientOps 对象
option := qmongoOpt.ClientOptions{
ClientOptions: opts,
}
// 使用 qmgo 中的 ClientOps 对象来创建一个 qmgo Client
client, err := qmgo.Open(ctx, &qmgo.Config{
Uri: mongoSrv,
Database: options.DbName,
MaxPoolSize: &maxNumber,
MinPoolSize: &minNumber,
}, option)
// 此后,所有使用该 client 进行的 MongoDB 操作都会自动进行插桩相关的操作和数据采集
// ...
}
从上述代码和描述中可以看到,Go 语言中 MongoDB 的插桩库是非常简单易用的。只需要在 MongoDB Client 的 Option 对象中设置 Monitor 属性为 otelmongo.NewMonitor() 即可完成自动插桩的过程。
Gin
Gin 是 Go 语言中非常流行的一个 Web 框架。
下面,我们来详细讲解一下在 Gin Web 服务中,如何实现 OpenTelemetry 的自动插桩。
Gin 框架提供了 Middleware 的机制来实现 AOP 编程,而 Gin 的 Otel 插桩库正是通过这一机制来实现了一个 Middleware 从而提供针对每个请求进行插桩的功能的。
首先,我们来安装相关的依赖:
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
下面,我们通过示例代码来演示一下相关的使用:
import (
"github.com/gin-gonic/gin"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)
func main() {
r := gin.Default()
r.use(otelgin.Middleware("demo")) // 引入 gin otel 插桩库
r.GET("/ping", func(c *gin.Context) {
// 在 Context 传递过程中,需要使用 c.Request.Context() 作为 context 向下传递
ctx, span = otel.Tracer("demo").Start(c.Request.Context(), "sleep")
time.Sleep(1 * time.Second)
span.End()
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
关于 Gin 框架的 Otel 插桩库需要主要需要注入如下两点:
- 通过 Middleware 的机制进行插桩,例如
r.use(otelgin.Middleware("demo"))
- 获取请求对应的包含当前 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 来实现自动插桩吧:
import (
"github.com/sirupsen/logrus",
"github.com/uptrace/opentelemetry-go-extra/otellogrus"
)
func main() {
// 通过 AddHook 函数来实现指定级别的日志自动插桩
logrus.AddHook(otellogrus.NewHook(otellogrus.WithLevels(
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
)))
// 需要传入对应包含 span 的 context 对象
ctx, span = otel.Tracer("demo").Start(c.Request.Context(), "sleep")
logrus.WithContext(ctx).Infof("hello world!")
span.End()
}
从上述代码中,关于 logrus 的自动插桩库的使用需要关注如下两点:
- 通过 logrus.AddHook 函数来实现自动插桩,插桩时还可以指定插桩生效的日志级别;
- 在打印日志时,需要对 logrus 调用 WithContext 函数来传入对应的 context 上下文后,对应的插桩库才能生效。