Distributed Tracing
Following one logical request across every service it touches by propagating a shared trace identifier and recording timed spans.
In a monolith a stack trace tells you where the time went. Across twelve services it does not exist. Tracing rebuilds it: an incoming request gets a trace ID, every service adds a span with its own start, duration, parent and attributes, and a backend assembles the spans into a tree.
This is the only practical way to answer "why was this specific request slow" in a distributed system, and it is a great deal more useful than aggregate latency metrics, which tell you that something is slow but not which hop.
The engineering cost is propagation discipline: the trace context must survive every hop including queues, thread pools and third-party clients, or the trace silently truncates. Sampling is the other decision — head-based sampling is cheap but may discard the slow requests you needed; tail-based sampling keeps them but requires buffering every span.