OpenTelemetry offers a zero-code (agent-based) way to instrument an application and a code-based way using its API. What does each actually capture, what can neither capture, and how do the two combine inside one process?
answer
- agent patches libraries at load time
- API in libraries, SDK in the app
- agent registers the global provider
- manual spans nest inside agent spans
- agent knows boundaries, not intent
basics
~20 sZero-code agents patch libraries at load time and emit spans for inbound requests, outbound calls, database and messaging clients with no source changes. Code-based instrumentation adds spans and attributes only your code knows. Both feed one SDK and nest.
solid answer
~50 s**Zero-code** means an agent or launcher hooks the runtime's loading mechanism — bytecode transformation on the JVM, module-load patching in Node.js, an import-time wrapper in Python, a profiler or startup hook in .NET — and wraps *libraries it recognises*. You get server spans for incoming requests, client spans for HTTP and database calls, producer/consumer spans for messaging, and propagation headers written and read on both sides, for zero source changes. What it cannot know is your domain: which tenant, which pricing branch, why a request took the slow path. **Code-based** instrumentation calls the OpenTelemetry API directly to open spans around your own logic and attach your own attributes. They are not alternatives. The agent installs the global provider, so your API calls resolve to that same live SDK: hand-written spans nest inside the agent's server span, share the trace, and leave through the same exporter. Neither invents coverage for a library nobody instrumented — that needs a hand-written wrapper.
go deeper
Say clearly that the agent instruments known libraries with no code change, manual instrumentation covers your own logic, and both write into the same SDK.
Name the injection mechanism for at least one runtime and stress that the agent is also what propagates context between services.
Talk about the coverage floor versus meaning trade-off, per-instrumentation toggles, overhead, and the libraries neither approach covers.
Frame it as a fleet policy: agent everywhere as the baseline, manual spend concentrated where domain decisions happen, with a documented attribute vocabulary.
## Something has to call the API A span exists only because code called the OpenTelemetry API. The two approaches differ purely in *who* makes that call. ## Zero-code (auto-instrumentation) A zero-code distribution injects instrumentation into libraries at load time, before your application logic runs: - **JVM**: a `-javaagent` jar rewrites bytecode of matched classes as they are loaded. - **Node.js**: the module loader is patched, so `require`/import of a known client returns a wrapped version. - **Python**: a launcher (`opentelemetry-instrument …`) patches supported libraries at import. - **.NET**: a CLR profiler or startup hook injects the same way. - **eBPF-based tooling** sits outside the process entirely and reconstructs spans from syscalls and sockets — no code changes at all, but it only sees what crosses the wire. What you get is the *boundary map* of the service: an inbound server span per request, a client span per outgoing HTTP or database call, producer and consumer spans for queues, plus context propagation injected into outgoing headers and extracted from incoming ones. That last part matters more than the spans: a single agent flag is what makes traces cross service boundaries at all. What you never get is intent. The agent knows a query took 300 ms; it does not know it was the fallback path for a cache miss on a premium tenant. ## Code-based (manual) Here you take a tracer from the provider and open spans yourself around units of work worth naming: a business transaction, a batch stage, an expensive computation, a retry loop as a whole. You attach attributes that only your code can supply, set span status on failure, and record exceptions. The important discipline is that libraries and frameworks depend on the **API only**, while the application supplies the **SDK**. That is what lets both sources land in one pipeline. ## How they compose In one process the agent registers the global `TracerProvider` during startup. When your code later asks for a tracer, it gets one backed by that provider rather than a no-op. Your manual span sees the agent's server span as the current context, so it becomes its child automatically — same trace id, same sampling decision, same exporter. Nothing needs to be wired between them. The practical consequence: reach for the agent first to get a coverage floor across every service cheaply, then hand-write the handful of spans and attributes that carry meaning. Teams that skip the agent spend months reinventing HTTP and database instrumentation; teams that stop at the agent get beautiful call graphs that cannot answer a single product question. ## Limits of both Neither approach covers an unrecognised library, an in-house RPC transport, or a thread hand-off the agent does not know about. Both cost something: the agent adds startup time and a small per-call overhead; manual spans cost developer time and, if placed inside hot loops, produce far more data than anyone will read.
- Your agent produced a server span for a request, and you also want your own span around a business step. Do you need to pass the agent's span into your code?No. The agent's span is the active context on that thread, so a tracer's span builder parents to it implicitly. You only pass context explicitly when you leave the thread or the request scope — a queue hand-off, an executor, a callback. Explicitly re-parenting to something you fetched yourself is usually a sign the context was lost, not that manual passing was required.
- How do you stop a noisy piece of auto-instrumentation without removing the agent?Distributions expose per-instrumentation toggles (an enabled flag keyed by the instrumentation's name) plus a global default-enabled switch to run in opt-in mode. That is preferable to removing the agent, which also removes context propagation. If the noise is volume rather than a specific library, the answer is sampling or a collector filter, not disabling instrumentation.
The agent is a building's CCTV at every door — it records who entered and left each room. Manual spans are the notes the people inside write about what they were doing and why.
saying these in an interview costs you the question
- Believing zero-code and manual instrumentation are competing choices you must pick between
- Thinking the agent can capture business attributes if you configure it hard enough
- Assuming a manual span needs the agent's span passed to it explicitly
- Claiming zero-code means zero overhead and zero configuration