You do not have to rewrite the monolith to see inside it. You do have to be careful, because a naive span on a hot path can cost more than the work it measures.

The monolith is not going anywhere this year, and it is where your incidents happen. The good news: you can instrument it richly without paying for it in latency, if you are deliberate.

Start at the seams

You do not need a span around every function. You need spans at the boundaries where time actually goes: inbound requests, database calls, cache lookups, and outbound HTTP. Those four cover most of what you will ever debug.

Watch the hot path

Instrumentation is not free. A span carries allocation, serialization, and export cost. On a function called ten thousand times a second, that adds up fast.

  • Sample aggressively on hot paths — you do not need every iteration to understand the shape.
  • Prefer cheap counters to full spans where you only need a rate.
  • Batch and export off the request path, never inline.

Measure the overhead you added

Instrument the instrumentation. We keep a benchmark that runs the critical path with tracing on and off, so we can prove the overhead stays under budget:

traces
| where service == "monolith"
| stats self_time = sum(duration - child_duration) by span.name
| sort self_time desc
| limit 20

Rich where it counts

Spend your span budget where incidents cluster. The checkout path deserves fine-grained tracing; the admin report that runs twice a day does not. Instrumentation is a resource to allocate, not a box to check.