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 tracingopen 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.

Using tracing, you can break down requests into spansopen 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.

To learn more about tracing, see Distributed Tracing using OpenTelemetryopen in new window.

What is OpenTelemetry?

OpenTelemetryopen in new window is an open-source observability framework hosted by Cloud Native Computing Foundation. It is a merger of OpenCensus and OpenTracing projects.

OpenTelemetry aims to provide a single standard across all types of observability signals such as OpenTemetry tracingopen in new window, and OpenTelemetry metricsopen in new window.

OpenTelemetry specifies how to collect and send telemetry data to backend platforms. With OpenTelemetry, you can instrument your application once and then add or change vendors without changing the instrumentation.

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

Creating spans

To measure performance of database queries or HTTP requests, you can create a span using OpenTelemetry Go API:

import "go.opentelemetry.io/otel"

var tracer = otel.Tracer("app_or_package_name")

func someFunc(ctx context.Context) error {
	ctx, span := tracer.Start(ctx, "some-func")
	defer span.End()

    // the code you are measuring

	return nil
}

You can also record attributesopen in new window and errors:

func someFunc(ctx context.Context) error {
	ctx, span := tracer.Start(ctx, "some-func")
	defer span.End()

    if span.IsRecording() {
        span.SetAttributes(
            attribute.Int64("enduser.id", userID),
            attribute.String("enduser.email", userEmail),
        )
    }

    if err := someOtherFunc(ctx); err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error())
    }

	return nil
}

See OpenTelemetry Go tracingopen in new window for details.

What is Uptrace?

Uptrace is an open source DataDog competitoropen in new window with an intuitive query builder, rich dashboards, alerting rules, and integrations for most languages and frameworks. It can process billions of spans and metrics on a single server and allows to monitor your applications at 10x lower cost.

Uptrace uses ClickHouse database to store traces, metrics, and logs. You can use it to monitor applications and set up automatic alerts to receive notifications via email, Slack, Telegram, and more.

You can get startedopen in new window with Uptrace by downloading a DEB/RPM package or a pre-compiled Go binary.

Example application

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

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

Configuring OpenTelemetry

Uptrace provides OpenTelemetry Go distro that configures OpenTelemetry SDK for you. To install the distro:

go get github.com/uptrace/uptrace-go

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

import "github.com/uptrace/uptrace-go/uptrace"

uptrace.ConfigureOpentelemetry(
    // copy your project DSN here or use UPTRACE_DSN env var
    //uptrace.WithDSN("https://<token>@uptrace.dev/<project_id>"),

    uptrace.WithServiceName("myservice"),
    uptrace.WithServiceVersion("v1.0.0"),
)

See documentation for details.

Instrumenting Gin

To instrument Gin router, you need a corresponding OpenTelemetry Gin instrumentationopen in new window:

import (
	"github.com/gin-gonic/gin"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)

router := gin.Default()
router.Use(otelgin.Middleware("service-name"))

otelgin instrumentation will save the active spanopen 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 attributesopen in new window:

func (h *Handler) Index(c *gin.Context) {
	ctx := c.Request.Context()

	// Extract span from the request context.
	span := trace.SpanFromContext(ctx)

    // Check if the span was sampled and is recording the data.
	if span.IsRecording() {
		span.SetAttributes(
			attribute.String("string_key", "string_value"),
			attribute.Int("int_key", 42),
			attribute.StringSlice("string_slice_key", []string{"foo", "bar"}),
		)
	}

	otelgin.HTML(c, http.StatusOK, indexTmpl, gin.H{
		"traceURL": otelplay.TraceURL(trace.SpanFromContext(ctx)),
	})
}

Instrumenting GORM

You can instrument GORM database client using otelgormopen in new window instrumentation:

import (
	"github.com/uptrace/opentelemetry-go-extra/otelgorm"
	"gorm.io/gorm"
)

if err := db.Use(otelgorm.NewPlugin()); err != nil {
	panic(err)
}

After the database is instrumented, you should use WithContext method to propagateopen in new window the active trace context:

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

Recording logs

You can also record log messages using otelzapopen in new window instrumentation for Zap logging library:

// Create Zap logger.
log := otelzap.New(zap.NewExample())

// Extract the active context from the request.
ctx := c.Request.Context()

// Use the logger and the context to record log messages on the active span.
log.Ctx(ctx).Error("hello from zap",
	zap.Error(errors.New("hello world")),
	zap.String("foo", "bar"))

// otelzap also supports an alternative syntax.
log.ErrorContext(ctx, "hello from zap",
	zap.Error(errors.New("hello world")),
	zap.String("foo", "bar"))
}

Running the example

You can start Uptrace backend with a single command using Docker exampleopen in new window:

docker-compose up -d

And then start the appopen in new window passing Uptrace DSN as an env variable:

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 APIopen in new window to create your own instrumentations or browse existing instrumentationsopen in new window provided by the community.

Popular instrumentations:

Last Updated: