Monitoring Gin and GORM with OpenTelemetry
Tags: monitoring, programming, performance, go
Published: 2023-08-01 08:06:35

Still using Jaeger/Sentry? Uptrace is an OpenTelemetry tracing tool that stores data in extremely efficient ClickHouse database and provides powerful filtering and grouping.

![Uptrace distributed tracing tool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0smxxm3aa4t6yvqiiy36.png)

---

* [What is tracing?](#what-is-tracing)
* [OpenTelemetry and Uptrace](#opentelemetry-and-uptrace)
* [Creating spans](#creating-spans)
* [Example application](#example-application)
* [Configuring OpenTelemetry](#configuring-opentelemetry)
* [Instrumenting Gin](#instrumenting-gin)
* [Instrumenting GORM](#instrumenting-gorm)
* [Recording logs](#recording-logs)
* [Running the example](#running-the-example)
* [What's next](#whats-next)

---

## OpenTelemetry and Uptrace

[OpenTelemetry](https://uptrace.dev/opentelemetry/) is a vendor-neutral API for distributed traces and metrics. You can use OpenTelemetry to collect traces, errors, and logs. Uptrace stores that data, aggregates and processes it to help you pinpoint failures and find performance bottlenecks.

Uptrace is an [open source APM](https://uptrace.dev/get/open-source-apm.html) and blazingly fast [distributed tracing tool](https://uptrace.dev/blog/distributed-tracing-tools.html) powered by OpenTelemetry and ClickHouse. It is a popular alternative to Jaeger and can be [installed](https://uptrace.dev/get/install.html) by downloading a DEB/RPM package or a pre-compiled binary.

## What is tracing?

[OpenTelemetry Tracing](https://uptrace.dev/opentelemetry/distributed-tracing.html#what-is-tracing) allows you to see how a request progresses through different services and components, timings of every operation, any logs and errors as they occur. In a distributed environment, tracing also helps you understand relationships and interactions between distributed micro-services and systems.

![Distributed tracing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fmm95e3niz8jive6t476.png)

Using tracing, you can break down requests into [spans](https://uptrace.dev/opentelemetry/distributed-tracing.html#spans). **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.

![Spans and trace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khnbmqbvyt6ph1m58iq7.png)

To learn more about tracing, see [Distributed tracing using OpenTelemetry](https://uptrace.dev/opentelemetry/distributed-tracing.html).

## Creating spans

You can create spans using [OpenTelemetry Go Tracing API](https://uptrace.dev/opentelemetry/go-tracing.html) like this:

```go
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
}
```

## Example application

In this tutorial, you will be instrumenting a [toy app](https://github.com/uptrace/uptrace/tree/master/example/gin-gorm) that uses Gin router and GORM database client. You can retrieve the source code with the following command:

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

## Configuring OpenTelemetry

Uptrace provides [OpenTelemetry Go](https://uptrace.dev/get/opentelemetry-go.html) distro that configures
OpenTelemetry SDK for you. To install the distro:

```shell
go get github.com/uptrace/uptrace-go
```

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

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

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

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

See [documentation](https://uptrace.dev/get/opentelemetry-go.html) for details.

## Instrumenting Gin

You can instrument Gin router using [OpenTelemetry Gin](https://uptrace.dev/get/instrument/opentelemetry-gin.html) instrumentation provided by OpenTelemetry:

```go
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 span](https://uptrace.dev/opentelemetry/distributed-tracing.html#context) 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 [attributes](https://uptrace.dev/opentelemetry/distributed-tracing.html#attributes):

```go
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 [OpenTelemetry GORM](https://uptrace.dev/get/instrument/opentelemetry-gorm.html#installation) instrumentation:

```go
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 [propagate](https://uptrace.dev/opentelemetry/go-tracing.html#context) the active trace context:

```go
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 [OpenTelemetry Zap](https://uptrace.dev/get/instrument/opentelemetry-zap.html) instrumentation for Zap logging library:

```go
// 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 example](https://github.com/uptrace/uptrace/tree/master/example/docker):

```go
docker-compose up -d
```

And then start the [app](https://github.com/uptrace/uptrace/tree/master/example/gin-gorm) passing Uptrace DSN as an env variable:

```shell
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 and GORM OpenTelemetry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whbvlazduhg60x93rcmu.png)

## What's next?

Next, you can learn about [OpenTelemetry Go Tracing](https://uptrace.dev/opentelemetry/go-tracing.html) to create your own instrumentations or browse existing [OpenTelemetry instrumentations](https://uptrace.dev/get/instrument/#go) provided by the community.

- [Context deadline exceeded](https://uptrace.dev/blog/context-deadline-exceeded.html)
- [OpenTelemetry vs Prometheus](https://uptrace.dev/blog/opentelemetry-vs-prometheus.html)