"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

ApproachEffortCoverage
Zero-code (auto-instrumentation)Lowest — no code changesBroad but generic: HTTP, DB drivers, frameworks
Instrumentation librariesLow — add a dependency, a few lines of setupTargeted at one library/framework
Manual instrumentationHighest — you write the API callsExactly 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:

  1. Start with zero-code instrumentation to get HTTP, database, and framework spans immediately.
  2. Add manual spans around business-critical operations that auto-instrumentation can't see (pricing logic, fraud checks, batch jobs).
  3. Add custom metrics for KPIs that matter to your product, not just infrastructure health.
💡
Manual spans nest inside auto spans automatically

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, not PaymentService.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 (startActiveSpan with a callback, context managers, using blocks) that end the span automatically, even on exception.
Takeaway

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.