ObservabilityXLIX · OpenTelemetry FoundationsOTelFoundations
SDKs by Language
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
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).
| Language | SDK status | Auto-instrumentation | Notable gaps |
|---|---|---|---|
| Java | GA | Yes (Java agent) | Native-image (GraalVM) limited |
| Python | GA | Yes (opentelemetry-instrument) | Async-runtime hooks partial |
| Go | GA | Manual only | No auto-instrumentation; contrib uses wrappers |
| .NET | GA | Yes (.NET profiler / activity APIs) | EventSource hook maturity uneven |
| Node.js | GA | Yes (auto loader) | Worker-thread context gaps |
| Ruby | Beta | Limited | Background-thread propagation gaps |
| PHP | Beta | Yes (auto loader) | Worker / CLI propagation gaps |
| Rust | Beta | Manual only | Tokio runtime hooks experimental |
| C++ | Experimental | Manual only | Single-thread profiling only |
| Swift | Experimental | Manual only | iOS-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-instrumentationplus the framework name (for example,-http,-grpc,-sqlalchemy); for the contrib repository, the meta-package isopentelemetry-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.
- 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.
- 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.Serveror call the API manually. The cost is the team’s, not the SDK’s. - 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.
- 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_spanscounter. - 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.
- 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.
- The propagation library mismatch. Service A uses
otelhttpfor outbound calls; service B usesreqwestwith the OTel request span middleware. One of the two propagates thetraceparentheader; 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. - 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.
- 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.
- 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. - Is the resource set?
traceqlfor the service name; an empty result means the resource was not set before the SDK emitted its first record. Fix at SDK initialisation. - Are the instrumentation libraries installed? For Java,
the agent classpath; for Python, the
opentelemetry-bootstrap -a installoutput; for Go, the imports in the application code. - Are the spans reaching the destination? Compare the
application’s
otelcol_exporter_sent_spanscounter 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.namemay 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 missingservice.nameis 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
Q1. Which OTel SDK is GA and ships the strongest auto-instrumentation?
Q2. Which layer does the application code call into?
Q3. The Go OpenTelemetry SDK ships an auto-instrumentation agent equivalent to the Java agent.
Q4. Which of these are real OTel Resource attributes that should be set at SDK initialisation?
Q5. Name the OTel contrib package for Python that patches supported frameworks in-process.
Q6. A Java service is compiled to a GraalVM native image. The OTel Java agent does not attach. Why?
Q7. A Go service has the OTel SDK installed but emits no spans. The most likely cause is:
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.