Skip to main content
RunBook Academy

ObservabilityXLIX · OpenTelemetry FoundationsOTelFoundations

SDKs by Language

Foundation⏱ ~18 minbash

What you'll learn

  • Name the languages the OpenTelemetry project ships an SDK for and the maturity of each
  • Distinguish the OTel API, the SDK, and an instrumentation library
  • Pick the right instrumentation strategy per language (auto, manual, or contrib)
  • Configure an SDK exporter with the OTLP endpoint, headers, and resource attributes
  • Recognise the failure shape that appears when SDK version skew or propagation library gaps break correlation

Prerequisites

Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13

Not yet marked complete on this device.

A team runs Go services for the data plane, Python services for the orchestration layer, Java services for the batch pipeline, and a Node.js frontend. They pick a tracing vendor in 2023. The vendor ships Go and Python SDKs as first-class; Java as beta-quality; Node.js only as an OpenTelemetry fork the team must patch themselves. By 2025 the Java instrumentation drops spans under load and the Node.js fork is two minor versions behind the upstream. The team faces the migration they were trying to avoid.

The OpenTelemetry project ships SDKs in nine languages under one API. The SDK maturity is not equal across languages — some are GA, some are in beta, one is experimental — and the right instrumentation strategy differs. The lesson that follows walks the matrix, names the maturity tier per language, and shows the configuration for the production-strongest choices.

What it is

The OpenTelemetry project maintains an SDK in nine languages. The SDK is the implementation of the OTel API; the API is the surface the application calls. The instrumentation libraries sit on top of the SDK and record spans for well-known frameworks (HTTP servers, database drivers, RPC frameworks).

LanguageSDK statusAuto-instrumentationNotable gaps
JavaGAYes (Java agent)Native-image (GraalVM) limited
PythonGAYes (opentelemetry-instrument)Async-runtime hooks partial
GoGAManual onlyNo auto-instrumentation; contrib uses wrappers
.NETGAYes (.NET profiler / activity APIs)EventSource hook maturity uneven
Node.jsGAYes (auto loader)Worker-thread context gaps
RubyBetaLimitedBackground-thread propagation gaps
PHPBetaYes (auto loader)Worker / CLI propagation gaps
RustBetaManual onlyTokio runtime hooks experimental
C++ExperimentalManual onlySingle-thread profiling only
SwiftExperimentalManual onlyiOS-only; server-side unsupported

Java and Python are the strongest: GA SDKs, mature auto- instrumentation, large contrib repositories. Go is GA but the project deliberately does not ship auto-instrumentation; the team must use wrappers or call the API directly. The remaining languages range from Beta to Experimental.

The Swift SDK is for iOS clients. The C++ SDK is for native daemons. Server-side Swift is not a target; server-side Rust is possible but the tracing story is largely manual.

API, SDK, and instrumentation library

The three layers are separate and addressable.

  • API — the surface the application code calls. Creating a span, recording a value, attaching an attribute. The API is no-op when no SDK is configured; that is the contract. The API package name is opentelemetry-api (or the language equivalent).
  • SDK — the implementation that registers the API calls, samples them, batches them, and exports them. The SDK is the part the operator configures. The SDK package name is opentelemetry-sdk.
  • Instrumentation library — a wrapper for a specific framework (HTTP, database, RPC) that records spans for every well-known operation. The instrumentation library package name follows the pattern opentelemetry-instrumentation plus the framework name (for example, -http, -grpc, -sqlalchemy); for the contrib repository, the meta-package is opentelemetry-instrumentation.

The split is deliberate. The application code calls only the API. The application can be compiled, tested, and deployed without the SDK on the classpath or in the dependency tree. Production deployment installs the SDK and the chosen instrumentation libraries; the same binary becomes observable.

Why a sysadmin cares

Three failure shapes appear when the SDK choice is per-service without a fleet-wide matrix.

  1. The Java agent that drops spans. The Java SDK is GA, but a specific version of the auto-instrumentation agent drops spans under a particular load pattern. The fix is to pin a known-good version, not to switch SDKs. Knowing the maturity tier tells the operator which bugs are likely and which are not.
  2. The Go service that emits no spans because there is no auto-instrumentation. The team follows the Java instrumentation pattern and expects the spans to appear. The Go SDK is GA but the project does not ship auto- instrumentation; the team must add a wrapper around http.Server or call the API manually. The cost is the team’s, not the SDK’s.
  3. The Node.js fork that drifts from upstream. The team adopts a vendor-flavoured OTel SDK because the upstream did not cover a specific framework. The fork falls behind; the migration to upstream becomes a project. The discipline is to prefer the contrib instrumentation repositories and to submit changes upstream when the team needs them.

A matrix the operator keeps in their head — or in a runbook — prevents all three. The matrix names the SDK per language, the auto-instrumentation strategy, and the known gaps.

How it works

The mental model. Every language has the same three layers (API, SDK, instrumentation libraries) but the package names and the entry points differ.

   Application
       |
       v
   +---------+      +-----------------+      +------------------+
   | API     | <--- | Instrumentation | <--- | Framework        |
   |         |      | library         |      | (HTTP, DB, RPC)  |
   +---------+      +-----------------+      +------------------+
       |
       v
   +---------+      +-----------------+
   | SDK     | ---> | Exporter (OTLP)  |
   +---------+      +-----------------+
       |
       v
   Resource, Sampler, BatchSpanProcessor, OTLP exporter

The instrumentation library and the application both call the API. The SDK records the API calls into records, applies the sampler, batches them, and exports them as OTLP.

The Go SDK

The Go SDK is go.opentelemetry.io/otel. The SDK is configured once at process start and passed to a global TracerProvider. Instrumentation libraries obtain a tracer from the provider and emit spans.

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func installTracing(ctx context.Context) (func(context.Context) error, error) {
    exporter, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint("otelcol:4317"),
        otlptracegrpc.WithInsecure(),
    )
    if err != nil {
        return nil, err
    }

    res, err := resource.Merge(
        resource.Default(),
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("checkout"),
            semconv.ServiceVersion("1.42.0"),
            semconv.DeploymentEnvironment("prod"),
        ),
    )
    if err != nil {
        return nil, err
    }

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    return tp.Shutdown, nil
}

The otel.SetTracerProvider call installs the SDK globally. Every tracer obtained from otel.Tracer("...") after this point emits through the configured exporter. The tp.Shutdown function flushes the queue on process exit.

The Java SDK

The Java SDK is io.opentelemetry:opentelemetry-api and the contrib repository is io.opentelemetry.instrumentation. The SDK is typically installed via the Java auto-instrumentation agent, which attaches to the JVM at start and instruments known frameworks.

# Attach the OTel Java agent to a JVM.
java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.service.name=checkout \
     -Dotel.service.version=1.42.0 \
     -Dotel.exporter.otlp.endpoint=http://otelcol:4317 \
     -jar checkout.jar

The agent reads configuration from environment variables and Java system properties. The service name, the exporter endpoint, the sampler, and the resource detectors are all controllable from the JVM start line.

The Python SDK

The Python SDK is opentelemetry-api and opentelemetry-sdk. The auto-instrumentation is shipped by the opentelemetry-instrument package, which patches known frameworks at process start.

# Install the API, SDK, and instrumentation libraries.
pip install opentelemetry-distro[otlp]
opentelemetry-bootstrap -a install

# Run the application with the auto-instrumentation activated.
opentelemetry-instrument \
    --service_name checkout \
    --service_version 1.42.0 \
    --exporter_otlp_endpoint http://otelcol:4317 \
    python main.py

The opentelemetry-instrument wrapper patches the supported frameworks in-process. The --service_name argument sets the service.name resource attribute; the --exporter_otlp_endpoint argument sets the OTLP destination.

Node.js, .NET, and the rest

Node.js uses the @opentelemetry/sdk-node package and the @opentelemetry/auto-instrumentations-node meta-package. The .NET SDK uses the OpenTelemetry NuGet package with AddOpenTelemetry().WithTracing(...) registration. Ruby and PHP ship SDKs in beta with manual instrumentation only. Rust and C++ ship beta / experimental SDKs respectively, with manual instrumentation as the rule.

How to configure it

The three production-strongest languages are Java, Python, and Go. Each has a different configuration surface. The other six languages are configured similarly but with manual hooks where auto-instrumentation is unavailable.

Go (programmatic configuration):

// Already shown in the SDK section above. Configure the global
// TracerProvider at process start with the OTLP gRPC exporter
// and the service resource.

Java (environment variables):

# /etc/checkout.env
OTEL_SERVICE_NAME=checkout
OTEL_SERVICE_VERSION=1.42.0
OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

Python (command-line arguments):

opentelemetry-instrument \
    --service_name checkout \
    --service_version 1.42.0 \
    --exporter_otlp_endpoint http://otelcol:4317 \
    --traces_exporter otlp \
    python main.py

The right choice per language depends on the maturity tier. For Java and Python, prefer auto-instrumentation. For Go, prefer wrappers around the framework’s RoundTripper / Handler types. For the remaining languages, prefer the contrib instrumentation libraries where they exist, and fall back to manual API calls.

How to validate it

Validate the SDK end-to-end by shipping a known trace and finding it in the destination.

Go (per-process):

# Run the application, then query the destination.
traceql '{ resource.service.name = "checkout" && span.http.route = "/healthz" }' | head -5
{ name = "GET /healthz", resource.service.name = "checkout",
  resource.service.version = "1.42.0",
  trace_id = "4bf92f3577b34da6a3ce929d0e0e4736" }

Java (smoke test):

# Curl the application's known endpoint. The Java agent
# instruments the inbound HTTP server, so a request to /healthz
# produces a span.
curl -s http://checkout:8080/healthz
# Find the span in the destination.
traceql '{ resource.service.name = "checkout" && name = "GET /healthz" }'

Python (smoke test):

# Run a single request through the Python application.
curl -s http://checkout:8080/healthz
# The destination shows the span.
traceql '{ resource.service.name = "checkout" }'

The smoke test for every language is the same shape: a known endpoint, a known response, a known trace ID in the destination. If the trace does not arrive, the SDK was not initialised or the OTLP endpoint is unreachable.

How it can fail

Five failure modes that arise when the SDK is the wrong version, the wrong language, or the wrong instrumentation strategy.

  1. The auto-instrumentation that drops spans under load. The Java agent has a known bug in a specific version where the batch processor rejects records under backpressure rather than queueing them. Symptom: a busy service emits only a fraction of its expected spans. The fix is to pin to a known-good agent version and to monitor the otelcol_exporter_dropped_spans counter.
  2. The Go service with no auto-instrumentation. The team adopts the OTel SDK in Go and expects spans to appear without code changes. The Go SDK does not ship auto- instrumentation. Symptom: the application emits no spans despite the SDK being installed. The fix is to add a wrapper around the framework’s HTTP client or server.
  3. The Node.js fork that drifts. The team adopts a vendor-flavoured OTel SDK for Node.js because the upstream did not cover a specific framework. Symptom: the fork falls behind the upstream; a security fix is missed. The fix is to migrate to the upstream auto-instrumentation and to submit the framework wrapper upstream.
  4. The propagation library mismatch. Service A uses otelhttp for outbound calls; service B uses reqwest with the OTel request span middleware. One of the two propagates the traceparent header; the other does not. Symptom: the trace across the boundary has the wrong parent or no parent at all. The fix is to enable the W3C TraceContext propagator on every HTTP client in the chain.
  5. The GraalVM native-image gap. The Java service is compiled to a native image with GraalVM. The OTel Java agent requires reflective access at runtime; the native image build excludes the agent classes. Symptom: the agent does not attach; the service emits no spans. The fix is to use the manual SDK configuration rather than the agent.

How to troubleshoot it

When an instrumented service emits no spans, the diagnostic order matters.

  1. Is the SDK initialised? Check the application startup log for the OTel SDK banner. The Go SDK does not log a banner by default; check the binary for the global TracerProvider.
  2. Is the exporter reachable? curl -v http://otelcol:4317 (gRPC returns HTTP/2 415 to a plaintext GET, which is the expected answer). A connection refused means DNS, firewall, or wrong port.
  3. Is the resource set? traceql for the service name; an empty result means the resource was not set before the SDK emitted its first record. Fix at SDK initialisation.
  4. Are the instrumentation libraries installed? For Java, the agent classpath; for Python, the opentelemetry-bootstrap -a install output; for Go, the imports in the application code.
  5. Are the spans reaching the destination? Compare the application’s otelcol_exporter_sent_spans counter against the destination’s span counter. A difference means the destination is rejecting the batch.

Security implications

The SDK carries attributes that may be sensitive.

  • Auto-instrumentation span attributes. HTTP path, query string, request body, response body, database statement, exception message. All are common span attributes; all may leak credentials or PII. The OTel Collector is the right place to redact; see lesson 06-pipeline-security.
  • OTLP exporter endpoint. The SDK ships telemetry to the OTLP endpoint over the network. In agent mode, the endpoint is localhost; in gateway mode, the endpoint is a remote host. The TLS configuration of the OTLP exporter is the encryption boundary.
  • SDK resource attributes. service.name, service.version, host.name may reveal internal naming conventions. A trace export to a third-party backend exposes the topology.

Performance implications

The SDK cost is on the hot path of every instrumented service.

  • Span volume. Every span emits a record. The cost is per request. Sampling is the mitigation. Head-based sampling on the SDK; tail-based sampling on the Collector.
  • Attribute cardinality. Auto-instrumentation can record high-cardinality attributes (URL paths with IDs, user IDs). The discipline is to cap attribute cardinality in the SDK or in the Collector.
  • Exporter batching. The batch span processor coalesces records to reduce per-call cost. The trade-off is latency against throughput. The default is a reasonable starting point; tune per service.
  • Auto-instrumentation overhead. The Java agent adds 1-3 percent CPU overhead on most workloads. The Python auto- instrumentation is heavier on cold paths. Measure before and after on the production workload.

Production guidance

  • Pin the SDK version per language. The SDKs release monthly. Read the release notes. Breaking changes to the API do happen.
  • Use the contrib instrumentation repositories. The contrib packages contain the auto-instrumentation and the framework wrappers. Prefer them over vendor forks.
  • Set the resource attributes at SDK initialisation. service.name, service.version, deployment.environment. A missing service.name is the highest-cost silent failure.
  • Sample traces by policy. Head sampling with a sensible rate for production traffic; tail sampling on the gateway for high-value traces.
  • Smoke test the SDK on every release. A known endpoint should produce a known trace ID in the destination within ten seconds. If it does not, the SDK is not correctly installed.

Verification

You should now be able to answer:

  • Which OTel SDKs are GA, which are Beta, and which are Experimental?
  • What is the difference between the OTel API, the SDK, and an instrumentation library?
  • Which two languages ship the strongest auto-instrumentation, and why?
  • Why does the Go SDK not ship auto-instrumentation?
  • What is the failure shape when a propagation library is missing on one of the services in the request path?

Quiz

Knowledge check · 8 questions

  1. Q1. Which OTel SDK is GA and ships the strongest auto-instrumentation?

  2. Q2. Which layer does the application code call into?

  3. Q3. The Go OpenTelemetry SDK ships an auto-instrumentation agent equivalent to the Java agent.

  4. Q4. Which of these are real OTel Resource attributes that should be set at SDK initialisation?

  5. Q5. Name the OTel contrib package for Python that patches supported frameworks in-process.

  6. Q6. A Java service is compiled to a GraalVM native image. The OTel Java agent does not attach. Why?

  7. Q7. A Go service has the OTel SDK installed but emits no spans. The most likely cause is:

  8. Q8. Which propagation library gap is the most common cause of a trace with a single span across multiple services?

Passing score: 75%. Answers are checked in this browser.