OpenTelemetry guide for Gin and GORM

In this article, you will learn how to use OpenTelemetry with Uptrace to monitor Gin and GORM performance.

Uptrace

What is tracing?

Distributed tracingGin and GORM - 图2open in new window allows you to see how a request progresses through different services and systems, timings of each operation, any logs and errors as they occur.

In a distributed environment, tracing also helps you understand relationships and interactions between microservices. Distributed tracing gives an insight into how a particular microservice is performing and how that service affects other microservices.

Gin and GORM - 图3

Using tracing, you can break down requests into spansGin and GORM - 图4open in new window. Span is an operation (unit of work) your app performs handling a request, for example, a database query or a network call.

Trace is a tree of spans that shows the path that a request makes through an app. Root span is the first span in a trace.

Gin and GORM - 图5

To learn more about tracing, see Distributed tracing using OpenTelemetryGin and GORM - 图6open in new window.

What is OpenTelemetry?

OpenTelemetryGin and GORM - 图7open in new window is an open source and vendor-neutral API for distributed tracingGin and GORM - 图8open in new window (including logs and errors) and metricsGin and GORM - 图9open in new window.

Otel specifies how to collect and export telemetry data in a vendor agnostic way. With OpenTelemetry, you can instrumentGin and GORM - 图10open in new window your application once and then add or change vendors without changing the instrumentation, for example, many open source tracing toolsGin and GORM - 图11open in new window already support OpenTelemetry.

OpenTelemetry is available for most programming languages and provides interoperability across different languages and environments.

Creating spans

You can create spans using OpenTelemetry Go APIGin and GORM - 图12open in new window like this:

  1. import "go.opentelemetry.io/otel"
  2. var tracer = otel.Tracer("app_or_package_name")
  3. func someFunc(ctx context.Context) error {
  4. ctx, span := tracer.Start(ctx, "some-func")
  5. defer span.End()
  6. // the code you are measuring
  7. return nil
  8. }

You can also record attributesGin and GORM - 图13open in new window and errors:

  1. func someFunc(ctx context.Context) error {
  2. ctx, span := tracer.Start(ctx, "some-func")
  3. defer span.End()
  4. if span.IsRecording() {
  5. span.SetAttributes(
  6. attribute.Int64("enduser.id", userID),
  7. attribute.String("enduser.email", userEmail),
  8. )
  9. }
  10. if err := someOtherFunc(ctx); err != nil {
  11. span.RecordError(err)
  12. span.SetStatus(codes.Error, err.Error())
  13. }
  14. return nil
  15. }

What is Uptrace?

UptraceGin and GORM - 图14open in new window is an open source and blazingly fast distributed tracing toolGin and GORM - 图15open in new window powered by OpenTelemetry and ClickHouse. It allows you to identify and fix bugs in production faster knowing what conditions lead to which errors

You can install UptraceGin and GORM - 图16open in new window by downloading a DEB/RPM package or a pre-compiled binary.

Example application

In this tutorial, you will be instrumenting a toy appGin and GORM - 图17open in new window that uses Gin router and GORM database client. You can retrieve the source code with the following command:

  1. git clone git@github.com:uptrace/uptrace.git
  2. cd example/gin-gorm

Configuring OpenTelemetry

Uptrace provides OpenTelemetry GoGin and GORM - 图18open in new window distro that configures OpenTelemetry SDK for you. To install the distro:

  1. go get github.com/uptrace/uptrace-go

Then you need to initialize the distro whenever you app is started:

  1. import "github.com/uptrace/uptrace-go/uptrace"
  2. uptrace.ConfigureOpentelemetry(
  3. // copy your project DSN here or use UPTRACE_DSN env var
  4. //uptrace.WithDSN("https://<key>@uptrace.dev/<project_id>"),
  5. uptrace.WithServiceName("myservice"),
  6. uptrace.WithServiceVersion("v1.0.0"),
  7. )

See documentationGin and GORM - 图19open in new window for details.

Instrumenting Gin

To instrument Gin router, you need a corresponding OpenTelemetry Gin instrumentationGin and GORM - 图20open in new window:

  1. import (
  2. "github.com/gin-gonic/gin"
  3. "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
  4. )
  5. router := gin.Default()
  6. router.Use(otelgin.Middleware("service-name"))

otelgin instrumentation will save the active spanGin and GORM - 图21open in new window in the Go context.Context. You can retrieve the context from the http.Request, extract the span from it, and use the span to record attributesGin and GORM - 图22open in new window:

  1. func (h *Handler) Index(c *gin.Context) {
  2. ctx := c.Request.Context()
  3. // Extract span from the request context.
  4. span := trace.SpanFromContext(ctx)
  5. // Check if the span was sampled and is recording the data.
  6. if span.IsRecording() {
  7. span.SetAttributes(
  8. attribute.String("string_key", "string_value"),
  9. attribute.Int("int_key", 42),
  10. attribute.StringSlice("string_slice_key", []string{"foo", "bar"}),
  11. )
  12. }
  13. otelgin.HTML(c, http.StatusOK, indexTmpl, gin.H{
  14. "traceURL": otelplay.TraceURL(trace.SpanFromContext(ctx)),
  15. })
  16. }

Instrumenting GORM

You can instrument GORM database client using otelgormGin and GORM - 图23open in new window instrumentation:

  1. import (
  2. "github.com/uptrace/opentelemetry-go-extra/otelgorm"
  3. "gorm.io/gorm"
  4. )
  5. if err := db.Use(otelgorm.NewPlugin()); err != nil {
  6. panic(err)
  7. }

After the database is instrumented, you should use WithContext method to propagateGin and GORM - 图24open in new window the active trace context:

  1. user := new(User)
  2. if err := h.db.WithContext(ctx).Where("username = ?", username).First(user).Error; err != nil {
  3. _ = c.Error(err)
  4. return
  5. }

Recording logs

You can also record log messages using otelzapGin and GORM - 图25open in new window instrumentation for Zap logging library:

  1. // Create Zap logger.
  2. log := otelzap.New(zap.NewExample())
  3. // Extract the active context from the request.
  4. ctx := c.Request.Context()
  5. // Use the logger and the context to record log messages on the active span.
  6. log.Ctx(ctx).Error("hello from zap",
  7. zap.Error(errors.New("hello world")),
  8. zap.String("foo", "bar"))
  9. // otelzap also supports an alternative syntax.
  10. log.ErrorContext(ctx, "hello from zap",
  11. zap.Error(errors.New("hello world")),
  12. zap.String("foo", "bar"))
  13. }

Running the example

You can start Uptrace backend with a single command using Docker exampleGin and GORM - 图26open in new window:

  1. docker-compose up -d

And then start the appGin and GORM - 图27open in new window passing Uptrace DSN as an env variable:

  1. UPTRACE_DSN=http://project2_secret_token@localhost:14317/2 go run .

The app should be serving requests on http://localhost:9999 and should render a link to Uptrace UI. After opening the link, you should see this:

gin-gorm-opentelemetry

What’s next?

Next, you can learn about OpenTelemetry Go APIGin and GORM - 图29open in new window to create your own instrumentations or browse existing instrumentationsGin and GORM - 图30open in new window provided by the community.

Popular instrumentations: