Uptrace for 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:
- Create a project.
- Configure uptrace-go with a DSN from the project settings page.
- Check instrumentations for Go frameworks and libraries.
- Instrument your code using OpenTelemetry API.
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()
}