Observability Wiki

OpenTelemetry

Auto-Instrumentation vs. Manual Instrumentation in OpenTelemetry

How OpenTelemetry's zero-code and manual instrumentation mechanisms differ, and how to combine them effectively.

Last updated

OpenTelemetry offers two distinct ways to get telemetry out of an application: auto-instrumentation, which adds spans, metrics, and logs to known libraries and frameworks without touching source code, and manual instrumentation, where a developer explicitly calls the SDK API to create spans, record metrics, or emit log records around code that matters to the business. Most real deployments use both, and knowing which mechanism does what is the key to instrumenting an application without either massive engineering effort or blind spots.

Why it matters

  • Auto-instrumentation gets you to useful telemetry fast. Attaching a Java agent or running opentelemetry-instrument in front of a Python process produces spans for incoming HTTP requests, outgoing calls, and database queries with zero code changes — often within minutes.
  • Manual instrumentation captures what generic libraries can’t know. No auto-instrumentation library knows that a particular function represents a business-critical step like order.fulfill or payment.authorize — only a developer can decide what’s worth its own span and which attributes matter.
  • Combining both avoids both extremes. Auto-instrumentation alone tends to produce noisy, framework-shaped traces with no business context; hand-writing every span is prohibitively expensive at scale. Layering manual spans onto an auto-instrumented baseline gets full coverage without the full cost.
  • The two share the same underlying context. Manual spans created inside a request that’s already being traced by auto-instrumentation automatically become children of the auto-generated span — there’s no manual wiring needed to connect them.

How it works

  • Java uses a javaagent JAR for true zero-code instrumentation. The agent attaches at JVM startup and rewrites bytecode as classes load, injecting instrumentation into calls to supported frameworks and libraries — the application’s own source and compiled bytecode are never modified on disk.
  • Python, .NET, PHP, and Go take a similar zero-code approach adapted to each runtime. Python’s opentelemetry-instrument command wraps a process at launch and monkey-patches supported libraries at import time; .NET and PHP have comparable startup-hook mechanisms; Go, lacking bytecode manipulation or true monkey-patching, historically relied on eBPF-based or library-wrapper approaches rather than a single drop-in agent, though this is an area of active development.
  • Manual instrumentation is the same core API in every language. A Tracer obtained from the SDK creates spans (start_span/startSpan), a Meter creates instruments for metrics, and a Logger emits structured log records — all following the same conceptual model regardless of language, so developers moving between codebases don’t relearn the concept each time.
  • Language maturity is uneven and still shifting. Tracing and metrics are stable across every major OpenTelemetry SDK, but coverage of zero-code instrumentation for specific frameworks varies — Java’s agent ecosystem is the most mature and broadest, with wide framework coverage; Python, Node.js, and .NET have strong and actively maintained zero-code support; Go’s story remains comparatively more manual because of language constraints. Always check a given library’s own instrumentation status before assuming auto-instrumentation covers it.
  • Instrumentation libraries sit between the two extremes. Rather than hand-writing spans for a popular library, OpenTelemetry-maintained or community instrumentation packages (for example for a specific HTTP client or ORM) can be registered explicitly in code — more deliberate than a drop-in agent, but far less work than manual spans for every call.

Combining both in practice

A practical rollout starts with zero-code instrumentation to get baseline traces, metrics, and service topology with minimal effort, then layers in manual spans around the handful of operations that matter most to the business or that auto-instrumentation can’t see — background jobs, custom queues, or domain logic with no corresponding library hook. The OpenTelemetry Demo application is a useful reference for seeing both approaches used together across multiple languages in one realistic system, and the OpenTelemetry Collector sits downstream of either approach unchanged, since both ultimately emit the same OTLP data.

Related tools