Skip to content

Uptrace for Go

uptrace-go

Installation

go get github.com/uptrace/uptrace-go

Introduction

uptrace-go is the official Uptrace client for Go that sends spans and errors to Uptrace.

If you are not familiar with tracing, please read Introduction to distributed tracing.

Quickstart

To get started with Uptrace:

Configuration

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

The following example shows how to create a tracer, start spans, and add events:

package main

import (
    "context"
    "errors"
    "fmt"
    "os"

    "github.com/uptrace/uptrace-go/uptrace"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/label"
)

func main() {
    ctx := context.Background()
    upclient := setupUptrace()

    defer upclient.Close()
    defer upclient.ReportPanic(ctx)

    // Use upclient to report errors when there is no active span.
    upclient.ReportError(ctx, errors.New("Hello from uptrace-go!"))

    // Create a tracer.
    tracer := otel.Tracer("github.com/my/repo")

    // Start active span.
    ctx, span := tracer.Start(ctx, "main span")

    {
        ctx, span := tracer.Start(ctx, "child1")
        span.SetAttributes(label.String("key1", "value1"))
        span.AddEvent(ctx, "event-name", label.String("foo", "bar"))
        span.End()
    }

    {
        ctx, span := tracer.Start(ctx, "child2")
        span.SetAttributes(label.String("key2", "value2"))
        span.AddEvent(ctx, "event-name", label.String("foo", "baz"))
        span.End()
    }

    span.End()
    fmt.Printf("trace: %s\n", upclient.TraceURL(span))
}

func setupUptrace() *uptrace.Client {
    upclient := uptrace.NewClient(&uptrace.Config{
        // copy your project DSN here or use UPTRACE_DSN env var
        DSN: "",

        ServiceName:    "service-name",
        ServiceVersion: "v1.0.0",
    })

    return upclient
}

Active span and context.Context

OpenTelemetry uses context.Context to pass around current active span (AKA in-process span context propagation). You should accept context.Context as the first argument and pass it down to other functions.

Usually you obtain a context from an HTTP request:

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

// Set context.
req = req.WithContext(ctx)

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)

Instrumenting your code

To report errors directly (without a span):

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

To create a tracer:

import "go.opentelemetry.io/otel"

// Create a named tracer using your repo as an identifier.
tracer := otel.Tracer("github.com/my/repo")

To create a span:

// context is used to store and pass around active span.
ctx := context.TODO()

// Create a span and save it in the context.
ctx, span := tracer.Start(ctx, "operation-name")

// Pass the context returned from the tracer so the active span is not lost.
err := doSomeWork(ctx)

// End the span when operation is complete.
span.End()

To get the active span from the context:

span := trace.SpanFromContext(ctx)

Once you have a span you can start adding attributes:

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

span.SetAttributes(
    label.String("enduser.id", "123"),
    label.String("enduser.role", "admin"),
)

And more complex events:

span.AddEvent(ctx, "log",
    label.String("log.severity", "error"),
    label.String("log.message", "User not found"),
    label.String("enduser.id", "123"),
)

To record an error, use RecordError which internally uses AddEvent:

if err != nil {
    span.RecordError(ctx, err)
}

Sampling

To sample a fraction (20%) of traces:

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

upclient := uptrace.NewClient(&uptrace.Config{
    // Sample 20% of all traces.
    Sampler: sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.2)),
})

You can also write a custom sampler to drop some traces by name:

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

// CustomSampler drops some traces based on their name and uses fallback sampler otherwise.
type CustomSampler struct {
    Fallback sdktrace.Sampler
}

func (s CustomSampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult {
    if params.Name == "trace2" {
        // Drop traces with such name.
        return sdktrace.SamplingResult{
            Decision: sdktrace.Drop,
        }
    }

    // For the rest use fallback sampler.
    return s.Fallback.ShouldSample(params)
}

func (s CustomSampler) Description() string {
    return s.Fallback.Description()
}