OpenTelemetry Spans in Go

OpenTelemetry comes with many instrumentation plugins for libraries and frameworks. This should be enough detail to get started with tracing in production.

As great as that is, you will still want to add additional spans to your application code, in order to break down larger operations and gain more detailed insights into where your application is spending its time.

When you create a new span to measure a subcomponent, that span is added to the current trace as the child of the current span, and then becomes the current span itself.

Tracer API

Accessing the tracer

In order to interact with traces, you must first acquire a handle to a Tracer.

By convention, Tracers are named after the component they are instrumenting; usually a library, a package, or a class.

import "go.opentelemetry.io/otel/api/global"

tracer := global.Tracer("package-name")

Note that there is no need to "set" a tracer by name before getting it. The name you provide is to help identify which component generated which spans, and to potentially disable tracing for individual components.

We recommend creating a Tracer once per component during initialization and retaining a handle to the tracer, rather than calling global.Tracer() repeatedly.

Accessing the current span

Ideally, when tracing application code, spans are created and managed in the application framework.

Assuming that your application framework is supported, a trace will automatically be created for each request, and your application code will already have a span available inside a the current context, which can be used for adding application specific attributes and events.

func printSpan(ctx context.Context) {
   span := trace.SpanFromContext(ctx)
   fmt.Printf("current span: %v\n", span)
}

You can access the current active span via the context object.

Setting a new current span

Let’s demonstrate creating a new span by example. Imagine you have an automated kitchen, and you want to time how long the robot chef takes to bake a cake. Create a new Bake-Cake span, and propagate it via a context object.

func BakeCake(ctx context.Context, tracer trace.Tracer) {
   // the new span will automatically become the child of the span in the current context, and be set as the current span in the new context.
   childCtx, childSpan := tracer.Start(ctx, "bake-cake")
   defer childSpan.End()

   PreheatOven(childCtx)
   MixBatter(childCtx)
   HeatCake(childCtx)
}