"Instrumentation" is the code that actually calls the OpenTelemetry API to produce telemetry. You rarely write it all by hand — most of it comes for free.
Three approaches
| Approach | Effort | Coverage |
|---|---|---|
| Zero-code (auto-instrumentation) | Lowest — no code changes | Broad but generic: HTTP, DB drivers, frameworks |
| Instrumentation libraries | Low — add a dependency, a few lines of setup | Targeted at one library/framework |
| Manual instrumentation | Highest — you write the API calls | Exactly what you ask for, anywhere in business logic |
Zero-code instrumentation
Several OpenTelemetry SDKs ship a "distro" or agent that hooks into the language runtime at startup and automatically patches common libraries — no source changes required. For Java, this is a javaagent attached at launch; for Python, the opentelemetry-instrument wrapper; for Node.js, getNodeAutoInstrumentations() registered before your app code runs.
# Java — attach the agent, zero code changes
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=checkout-api \
-Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
-jar checkout-api.jar
# Python — wrap the entry point
opentelemetry-instrument --service_name checkout-api python app.py
Zero-code instrumentation typically covers: incoming/outgoing HTTP requests, database client calls, common web frameworks (Express, Flask, Spring), and message queue clients. It's the fastest way to get a useful baseline of traces from an existing application.
Instrumentation libraries
Between "zero code" and "fully manual" sit dedicated instrumentation libraries — packages published under the opentelemetry-instrumentation-* naming convention that specifically target one framework or library (e.g. @opentelemetry/instrumentation-express, opentelemetry-instrumentation-requests). You register these explicitly in your SDK bootstrap rather than relying on the auto-attach agent, giving you more control over which libraries are patched.
Manual instrumentation
Zero-code instrumentation knows nothing about your business logic — it can tell you an HTTP handler ran, but not that it was processing a "checkout" or applying a "discount." Manual instrumentation fills that gap:
const tracer = trace.getTracer('checkout-service');
async function applyDiscount(order, code) {
return tracer.startActiveSpan('apply-discount', async (span) => {
span.setAttribute('discount.code', code);
try {
const result = await discountEngine.evaluate(order, code);
span.setAttribute('discount.amount', result.amount);
return result;
} finally {
span.end();
}
});
}
Manual instrumentation is also how you add custom metrics (business KPIs like "orders processed" or "carts abandoned") and structured log attributes that framework auto-instrumentation has no way of knowing about.
Combining zero-code with manual spans
The recommended real-world approach is layered, not either/or:
- Start with zero-code instrumentation to get HTTP, database, and framework spans immediately.
- Add manual spans around business-critical operations that auto-instrumentation can't see (pricing logic, fraud checks, batch jobs).
- Add custom metrics for KPIs that matter to your product, not just infrastructure health.
Because both zero-code and manual instrumentation use the same underlying Context mechanism, a manually created span inside an auto-instrumented HTTP handler is automatically parented correctly — you don't need to manually pass span references around within a process.
Best practices
- Name spans by operation, not by implementation detail.
process-payment, notPaymentService.java:142. - Use semantic conventions for attribute names (covered in the next chapter) rather than inventing your own — this keeps telemetry portable across backends and tools.
- Don't over-instrument hot loops. Creating a span per iteration of a tight loop adds overhead disproportionate to the insight gained; instrument at a coarser granularity and use attributes/events for detail.
- End every span you start — most SDKs offer helpers (
startActiveSpanwith a callback, context managers,usingblocks) that end the span automatically, even on exception.
Get broad, generic coverage for free with zero-code instrumentation, then invest manual effort only where it adds unique business insight auto-instrumentation can't see.