OpenTelemetry Spans in JavaScript

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.

const tracer = opentelemetry.trace.getTracer('my-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 calling getTracer once per component during initialization and retaining a handle to the tracer, rather than calling getTracer 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 be wrapped in a span, which can be used for adding application specific attributes and events.

To access the currently active span, call getCurrentSpan

const tracer = opentelemetry.trace.getTracer('launcher-node-example');
const span = tracer.getCurrentSpan();

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. The naive way to do this would be to just start a span, call your method, then end the span:

// make a child span to measure how long it takes to bake a cake.
Const parentSpan = tracer.getCurrentSpan()

Const cakeSpan = tracer.startSpan('bake-cake', {parent: parent_span});
chef.bakeCake()
cakeSpan.end()

The above example will work just fine, but with one big problem: the bakeCake method itself has no access to this new bake-cake span. That means there would be no way to add attributes and events to this span from within the bakeCake method. Even worse, get_current_span would return the parent of “bake-cake,” since that span is still set as current.

What should we do instead? Replace the current span with bake-cake. To do this, call withSpan to make a closure around the bakeCake method. Within this closure, the getCurrentSpan method will now return bake-cake.

// Replace the current active span with a new child span
Const cakeSpan =  tracer.startSpan("bake-cake"):
tracer.withSpan()
  # now returns the bake-cake span
  tracer.getCurrentSpan()
  # bake your cake!
  chef.bake_cake()

This pattern of wrapping method calls is important, because we always want application code to be able to assume that the current span is correct.