You find a slow trace. Now you want its logs. In a siloed stack that means copying a timestamp, switching tabs, and hoping the clocks agree. In a unified one it means adding a line to your query.
The join key is the trace id
If your logs carry the trace id of the request that produced them — and they should — then logs and traces are just two tables with a shared key. Correlation stops being a manual hunt and becomes a database operation.
One query, both signals
traces
| where service == "checkout" and duration > 2s
| join (logs | where level == "error") on trace_id
| project ts, route, duration, log.message
| sort duration desc
Why it matters at 3 a.m.
Under pressure, every context switch is a chance to lose the thread. "Show me the error logs for the requests that were slow" is the question you actually have. Being able to type it, instead of assembling it by hand across two tools, is the difference between a five-minute and a fifty-minute investigation.
The prerequisite
- Propagate trace context through every hop, including async boundaries.
- Inject the trace id into your structured log fields automatically, not by hand.
- Store both signals where a single query engine can reach them.
Do that once and correlation is free forever. Skip it and you pay the swivel-chair tax on every incident.