# OpenTelemetry Go distro for Uptrace

# Prerequisites

This tutorial assumes you have installed and configured OpenTelemetry SDK by following the Getting started guide.

# Installation

To install uptrace-go:

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

# Configuration

You configure Uptrace distribution using a DSN (Data Source Name, e.g. https://<token>@api.uptrace.dev/<project_id>) from the project settings page.

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

uptrace.ConfigureOpentelemetry(&uptrace.Config{
    // copy your project DSN here or use UPTRACE_DSN env var
    DSN: "",

    ServiceName:    "myservice",
    ServiceVersion: "v1.0.0",
})
1
2
3
4
5
6
7
8
9

The full list of available configuration options can be found in config.goopen in new window.

OptionDescription
DSNA data source that is used to connect to uptrace.dev. For example, https://<key>@api.uptrace.dev/<project_id>.
ServiceNameservice.name resource attribute. For example, myservice.
ServiceVersionservice.version resource attribute. For example, 1.0.0.
ResourceAttributesAny other resource attributes.
ResourceResource contains attributes representing an entity that produces telemetry. Resource attributes are copied to all spans and events.

You can also use environment variables to configure the client:

Env varDescription
UPTRACE_DSNA data source that is used to connect to uptrace.dev. For example, https://<key>@api.uptrace.dev/<project_id>.
OTEL_RESOURCE_ATTRIBUTESKey-value pairs to be used as resource attributes. For example, service.name=myservice,service.version=1.0.0.
OTEL_PROPAGATORSPropagators to be used as a comma separated list. The default is tracecontext,baggage.

# OpenTelemetry API

All the code below is also available as a runnable exampleopen in new window.

# Creating a tracer

To start creating spans, you need a tracer. You create a tracer by specifying a tracer name (AKA instrumentation library name):

import "go.opentelemetry.io/otel"

var tracer = otel.Tracer("app_or_package_name")
1
2
3

You can have as many tracers as you want. Use the tracer name to identify the library that produces the spans.

# Creating a span

Once you have a tracer, creating spans is easy:

import "go.opentelemetry.io/otel/trace"

// Create a span with name "operation-name" and kind="server".
ctx, span := tracer.Start(ctx, "operation-name", trace.WithSpanKind(trace.SpanKindServer))

// End automatically sets the span end time.
defer span.End()

doSomeWork()
1
2
3
4
5
6
7
8
9

# Adding span attributes

To record contextual information you annotate spans with attributes that carry information specific to the operation. For example, an HTTP endpoint may have such attributes as http.method = GET and http.route = /projects/:id.

// To avoid expensive computations, check that span is recording
// before setting any attributes.
if span.IsRecording() {
	span.SetAttributes(
		attribute.String("http.method", "GET"),
		attribute.String("http.route", "/projects/:id"),
	)
}
1
2
3
4
5
6
7
8

You can name attributes as you want, but for common operations you should use semantic attributesopen in new window convention. It defines a list of common attribute keys with their meaning and possible values.

# Adding span events

You can annotate spans with events that have start time and arbitrary number of attributes. The main difference between events and spans is that events don't have end time (and therefore no duration).

Events usually represent exceptions, errors, logs, and messages (such as in RPC). But you can record custom events as well.

import "go.opentelemetry.io/otel/trace"

span.AddEvent("log", trace.WithAttributes(
	attribute.String("log.severity", "error"),
	attribute.String("log.message", "User not found"),
	attribute.String("enduser.id", "123"),
))
1
2
3
4
5
6
7

# Recording errors

OpenTelemetry provides a shortcut to record an error.

if err != nil {
    // Record the error and update the span status.
    span.RecordError(err)
	span.SetStatus(codes.Error, err.Error())
}
1
2
3
4
5

Occasionally you may want to record an error when there are no active spans. In such case use uptrace.ReportError to report the error without creating an association with a span:

uptrace.ReportError(ctx, errors.New("Hello from Uptrace!"))
1

# Trace context and the active span

OpenTelemetry stores the current active span in a context.Contextopen in new window. You propagate the context by passing it as a first function argumentopen in new window. You can nest contexts inside each other and OpenTelemetry will automatically activate the parent span context when you end the span.

Usually you obtain the context from an HTTP request:

// Get the context.
ctx = req.Context()

// Set the context.
req = req.WithContext(ctx)
1
2
3
4
5

tracer.Start sets the active span for you, but you can also activate the span manually:

import "go.opentelemetry.io/otel/trace"

// Get the active span from a context.
span = trace.SpanFromContext(ctx)

// Save the active span in a context.
ctx = trace.ContextWithSpan(ctx, span)
1
2
3
4
5
6
7

# Sampling

To collect only half of the traces:

import sdktrace "go.opentelemetry.io/otel/sdk/trace"

uptrace.ConfigureOpentelemetry(&uptrace.Config{
	// Sample 50% of all traces.
	Sampler: sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.5)),
})
1
2
3
4
5
6

# What is next?

By now, you should have a configured OpenTelemetry distribution and a solid grasp of OpenTelemetry API. Next, use that knowledge to instrument your code and check available instrumentationsopen in new window.