Span links: connecting spans that aren't parent and child

3 min read

Short answer

A span link points from one span to another span's context, in the same trace or a different one, with optional attributes. A parent says 'this work is part of that work'. A link only says the two are causally related. Links fit work that starts in one place and finishes somewhere else, such as a transaction sent by an agent and confirmed by a background job.

On this page

An agent sends a payment and moves on. Four minutes later, a worker sees the receipt. That confirmation isn’t part of the agent’s run anymore, but you still want to get from one to the other.

A send span in the agent's trace and a confirm span that starts a new trace, connected by an OpenTelemetry span link that carries the send span's trace ID and span ID.
Parent Link
How many One at most Any number
Same trace? Always Same or different
Changes the tree? Yes, it’s the tree No
Says “I’m part of that work” “That work caused me”

A link is the target span’s context plus optional attributes (trace API spec). The OpenTelemetry docs describe links as implying a causal relationship, and they recommend them for asynchronous work whose later traces you can’t predict (traces).

A confirm span in a different trace

Here the agent’s trace sends the transaction, and a receipt watcher confirms it later in a new trace. The link carries the send span’s trace ID and span ID:

import { ROOT_CONTEXT, SpanKind } from '@opentelemetry/api';
// The agent run sends the transaction and moves on.
const send = tracer.startSpan('send 8453', { kind: SpanKind.CLIENT });
send.end();
// Later, a receipt watcher confirms it in a trace of its own.
const confirm = tracer.startSpan(
'confirm 8453',
{ kind: SpanKind.CLIENT, links: [{ context: send.spanContext() }] },
ROOT_CONTEXT,
);
confirm.end();
send 8453 45d7a0fc587ce8fddf7a55856c9709d7
confirm 8453 6845556a628be3d8e8da59c40ebdf909 -> 45d7a0fc587ce8fddf7a55856c9709d7 58861896631ec682

Two trace IDs, one link between them. Add links when the span starts if you can: a head sampler decides at creation and never sees links added later.

Why not one long span?

You could keep the send span open until the receipt arrives. On Anvil that’s milliseconds; on a busy L1 it can be minutes, and an agent often doesn’t wait at all. If the process exits first, the open span is lost. Two short spans plus a link don’t have that problem.

FAQ

What is the difference between a span link and a parent?

A span has at most one parent, and the parent defines where it sits in the tree and which trace it belongs to. A span can have many links, to spans in any trace, and they don't change its position.

When should I add a span link?

When the span starts, if you already have the other span's context. Head samplers only see what's there at creation. The API also has addLink for contexts you learn later.

Can a span link to a span in another trace?

Yes. The linked span context carries its own trace ID, so a backend can open the other trace from the link.

Go further

Type to search the docs, Learn topics and the blog.