skip to content

OpenTelemetry ships a tracing API artifact separately from its SDK. Why is the split there, what should a reusable library depend on, what happens at runtime when no SDK is installed, and what is the tracer's name and version used for?

level: middleimportance: should knowfreq 45%

answer

  1. API = interfaces + no-op; SDK = sampler/processor/exporter
  2. libraries depend on API, apps install SDK
  3. no SDK ⇒ non-recording spans, context still valid
  4. tracer name/version = instrumentation scope on every span
  5. schema URL declares semconv version

basics

~20 s

The API is the calling surface; the SDK is the implementation that samples, processes and exports. Libraries depend on the API only, so with no SDK present every call is a cheap no-op. The tracer's name and version identify the instrumentation scope on each span.

solid answer

~50 s

The **API** defines tracers, spans, context and propagators. The **SDK** is one implementation of it: samplers, span processors, exporters, resource. The split exists so a library can be instrumented without forcing an observability stack on its users. If an application never installs an SDK, the API's default provider returns non-recording spans — valid span contexts, no allocation-heavy work, no export — so the instrumented library is safe to depend on unconditionally. Only the **application** (or an agent acting on its behalf) installs and configures the SDK, and it does so once, early. Instrumentation acquires a tracer by name and version — the **instrumentation scope** — usually the instrumenting module's own identifier. The scope is recorded on every span it produces, which lets a backend attribute telemetry to the code that emitted it, and lets SDK-side configuration target one noisy instrumentation without touching others. Scopes may also carry a schema URL declaring which semantic-convention version their attributes follow.

go deeper

for a junior

State the split — API is what you call, SDK is what implements it — and that libraries use the API while the app installs the SDK.

for a middle

Explain the no-op default and non-recording spans, and that the tracer name/version becomes the instrumentation scope stamped on spans.

for a senior

Add the ordering hazard of acquiring a tracer before SDK registration, and how scope enables targeted configuration and duplicate-span triage.

for a principal

Discuss it as an ecosystem contract: API stability guarantees are what make third-party libraries willing to instrument natively, and scope plus schema URL are the basis for automated convention migration.

## Why two artifacts OpenTelemetry wants library authors to instrument their own code. That only works if depending on OpenTelemetry costs a library's users nothing when they do not want telemetry. Hence the split: - **API**: interfaces and a no-op default — `TracerProvider`, `Tracer`, `Span`, `Context`, `Propagator`. Stable, slow-moving, safe as a compile-time dependency. - **SDK**: the working implementation — sampler, span processors, exporters, resource, limits. Chosen and configured by the application. The rule that falls out: **libraries depend on the API, applications depend on the SDK.** A library that pulls in the SDK forces its exporter and configuration choices onto every consumer and can collide with the application's own setup. ## The no-op path With no SDK registered, the API's default provider hands back a tracer that produces **non-recording spans**. A non-recording span still carries a valid span context, so propagation and parenting keep working, but it records no attributes and is never exported. The cost is close to zero, which is what makes unconditional instrumentation acceptable. This has an ordering consequence: a component that grabs a tracer *before* the SDK is installed can hold a no-op forever in implementations that resolve eagerly. Modern SDKs mitigate this with lazily-resolving proxies, but the safe habit remains — install the SDK first, or fetch the tracer at use time. ## Instrumentation scope When you ask for a tracer you pass a **name**, optionally a **version**, and optionally a **schema URL**. Together these are the instrumentation scope, and every span the tracer emits carries it. Conventionally the name is the instrumenting library's identifier, not the service name and not the class name — the point is to identify *the instrumentation*, not the workload (the workload is identified by the resource). Why it matters in practice: - A backend can show which instrumentation produced a span, which is how you tell an agent's HTTP client span from your own wrapper. - SDK-side configuration can be scoped: metric views and, in newer configuration surfaces, per-scope enable/disable. One chatty instrumentation can be turned down without disabling everything. - The schema URL records which semantic-convention version the attributes conform to, which is what makes automated attribute translation across convention versions possible. The metrics and logs signals mirror the structure exactly: a `MeterProvider` yields meters with the same scope triple, a `LoggerProvider` yields loggers for log-bridge implementations. Same separation, same no-op default. ## Practical shape of an application The application, once at startup, builds the SDK's providers (tracer, meter, logger), attaches processors and exporters, and registers them globally so library instrumentation can find them. Everything else in the process — your code and every instrumented dependency — just asks for a tracer by scope and uses it. When an agent is present it performs that registration for you, which is why agent-installed and hand-written instrumentation land in the same pipeline. The failure mode to recognise: spans that appear to be created but never arrive, with no error anywhere. That is almost always the no-op provider — either no SDK on the path, or instrumentation that captured a tracer before registration.

  • A library author asks whether to expose a configuration hook so users can pass in a tracer. What do you advise?
    Usually not. The library should acquire a tracer from the global provider under its own instrumentation scope, so it works with whatever the application installed and identifies itself correctly. An injection hook is worth it only for libraries that must run before global registration, or in ecosystems where global state is discouraged; even then, default to the global provider.
  • You see spans in the backend attributed to an instrumentation scope you do not recognise. How is that useful?
    The scope names the code that created the span, so it tells you whether an agent's built-in instrumentation, a third-party library's own instrumentation, or your wrapper produced it. That distinguishes duplicate spans around the same call, and it gives you the handle to disable or down-configure exactly one source.

The API is a wall socket standard: appliances are built against it whether or not the building is wired. The SDK is the wiring and the meter — installed once, by the building owner.

saying these in an interview costs you the question

  • Having a reusable library depend on the SDK rather than the API
  • Thinking that with no SDK installed the instrumented code will throw or fail
  • Using the service name as the tracer name — the resource identifies the service, the scope identifies the instrumentation
  • Installing or reconfiguring the SDK from library code

context