rebuilding trees with span links

Home Feed
2026-10-25 observability

So when a service needs to take payment, the control moves through its own processes and out across the network. At the moments that matter, the developer adds print statements to flush some logs at runtime. Each log is a span in the telemetry world, and the timestamp tells you when the event occurred.

The payment service is usually run by a different company. Your backend calls makePayment(card, amount) and the call leaves the system. Control is no longer inside your app. The service waits, the payment service does its work, and a response comes back. Until that moment, you can keep a tree of spans, but once the call leaves, the tree stops. The internals of makePayment are invisible and you only get the response. The same break happens across microservices. One service’s code is not visible to the next. They talk through RPCs or queues.

In the early days of Dapper, the developers at Google wanted a view of the entire request, even when it spanned multiple machines. Spans share a trace ID when linked. When everything stays inside systems you control and stays synchronous, you can see through to the end by travelling the tree. When the work is async, or when control leaves and comes back later, the pure tree does not hold. The logs can stop in the middle.

Span links are one of the ways to preserve the logs. One common way to "link" spans is by inserting the ID of the last span of the previous trace into the first span in the next trace. Even though neither side shares a continuous stack, the ID is just carried across, so the logical connection does not die when control leaves and returns. The spans stay loosely coupled because the control itself is loosely coupled. The idea here is to poorly map the distributed traces to a linear DAG to visualisee the control flow. You can still walk from the initial request to the final response, but the graph has gaps and extra edges instead of a single hierarchy. That is useful when response arrives async.