← Back to blog
Product 4 min read 炬鲸团队

Inside Distributed Tracing: From a Single Request to Service Topology and Slow-Call Detection

A look inside OBSERVE's tracing engine: trace and span structure, automatic service topology, slow-call and error analysis, and one-click correlation of traces with logs and metrics, plus sampling and cost controls.

The data structure behind one request

Distributed tracing boils down to two concepts: Trace and Span. A frontend request enters the gateway, fans out through the order service, inventory service, and database, and produces one Trace. Each service call inside it is a Span; span_id links parent and child, and trace_id ties the whole chain together. OBSERVE's ingestion accepts Span data over OTLP and stores trace_id, span_id, parent_span_id, service.name, duration, status code, and arbitrary attributes — every downstream analysis builds on these fields.

One design detail matters: Span attributes accept arbitrary key-value pairs, such as order_id, user_id, or region. Instrument these business IDs into your spans at the source, and you can later search the entire chain by "order number" instead of dredging through millions of spans.

A second, easily missed detail is span kind. A single call between two services produces both a client Span and a server Span, and the topology edge is stitched from those two sides; message queues add producer/consumer kinds. Mark exceptions explicitly with span.set_status(ERROR) at instrumentation time so error-rate analysis is accurate instead of guessing from HTTP status codes.

Service topology: stop guessing at the call graph

Once you have dozens of microservices, keeping track of who calls whom — and where the slowness is — becomes impossible to do by hand. OBSERVE generates the service topology from span parent-child relationships and service.name: nodes are services, edges are calls, edge weight reflects QPS, and color reflects error rate or latency. The graph is live, so services coming and going, or call paths changing, update automatically.

The topology earns its keep during incidents: when a downstream slows down and upstream queues pile up, you can see which node is the bottleneck at a glance, then click through to the offending spans. In quieter times it also surfaces dead code — old services nobody calls anymore that are still sending requests. When you ship a release, diff the topology before and after to confirm nothing dropped off unexpectedly or drifted to a new call path.

Slow-call and error analysis

Topology tells you where; you still need to know why. OBSERVE drills into slow calls from three angles:

  • By service, looking at P50/P95/P99 latency distributions to find the long tail;
  • By span type, separating slow databases from slow caches and slow downstream HTTP calls;
  • By individual span, breaking down each call's time to a specific SQL statement or endpoint.

A concrete example: order-api's P99 spikes. Split by span type and you see 80% of the time sits in a downstream call to inventory-service; open that span and the breakdown shows "waiting for response" dominating; then read the logs sharing the trace_id and confirm the inventory service is waiting on a database lock. Three steps to the root cause.

Error analysis works the same way: when the error rate spikes, aggregate by service.name, http.status_code, and exception type to find which service and which error class, then drill into a single erroring trace for full context.

Correlating traces with logs and metrics

The most common debugging path is: a dashboard shows P99 spiking → you open tracing to see which endpoint is slow → you need logs to confirm the actual error. If those three live in three separate systems, the context-switching eats your time. OBSERVE's approach is to carry trace_id on both logs and spans, showing all logs a trace produced right on the trace detail page, and letting a metric anomaly jump to related trace samples in one click. The reverse works too: from an erroring log line you can find its trace and its upstream caller.

The precondition is injecting trace_id into logs — every mainstream logging framework supports MDC/context. If you skip that, traces and logs stay two disconnected worlds no matter what the platform offers.

Finding the right trace

When an alert says "order endpoint timing out," the search path is: filter slow traces by service.name=order-service and a latency threshold, see which downstream they share; lock onto the suspect span, expand its breakdown to see whether it's waiting on a database, a downstream, or its own compute; then open the logs with the same trace_id to find the stack trace. With practice you can go from alert to root cause in a few minutes. Save your frequent investigation paths as saved views so the on-call engineer doesn't rebuild the query every time.

Sampling and cost

Storing every trace will blow up your bill, especially for high-QPS services. Our recommendation: use tail sampling in production — keep 100% of traces that carry an error or exceed a latency threshold, and sample 1%-10% of normal requests; raise the rate temporarily during investigations. OBSERVE lets you configure sampling by service.name, duration, and error status, and cold data archives to object storage automatically to keep costs down. Plan sampling and instrumentation together: good instrumentation is what lets sampling keep the high-value traces, otherwise you either pay too much or sample away the traces that matter.