OpenTelemetry mit Claude Code 2026

OpenTelemetry ist der offene Standard für Observability — vendor-neutral, für alle Backends kompatibel. Claude Code kennt das OTel-SDK für Node.js in- und auswendig: Auto-Instrumentation, Custom Spans, Metrics und Distributed Tracing für produktionsreife Anwendungen.

1. OTel Konzepte — Traces, Spans, Metrics, Logs

OpenTelemetry (kurz: OTel) ist das CNCF-Projekt, das Observability-Daten standardisiert. Statt proprietärer SDKs für Datadog, New Relic oder Dynatrace schreibst du einmal OTel — und wählst danach frei dein Backend. Das ist der Paradigmenwechsel, den 2026 endgültig alle größeren Node.js-Stacks vollzogen haben.

TRACE Das OTel-Datenmodell auf einen Blick

OTel kennt drei Signaltypen — jeder hat einen eigenen Exportpfad, aber alle teilen denselben Kontext (TraceID, SpanID):

  • Traces — Eine Folge von Spans, die eine einzige Request-Reise durch alle Services beschreiben. Die TraceID verbindet alles.
  • Spans — Einzelne Operationen innerhalb eines Traces (z.B. "HTTP GET /users", "DB SELECT"). Jeder Span hat Start, Ende, Attribute und Status.
  • Metrics — Aggregierte Messwerte über Zeit: Counter (monoton steigend), Histogram (Verteilung), Gauge (aktueller Wert).
  • Logs — Strukturierte Ereignisse, die optional an einen aktiven Span gebunden werden (Korrelation via TraceID).

KONZEPT Span-Hierarchie und TraceID

Jeder Span kennt seinen Parent-Span. So entsteht ein Baum: Root-Span (z.B. der HTTP-Handler) → Kind-Spans (DB-Query, externes API-Call, Cache-Lookup). Die TraceID ist für alle Spans gleich — sie macht das Distributed Tracing möglich, auch über mehrere Services hinweg.

// Trace-Struktur (konzeptuell) Trace abc123 └─ Span: POST /checkout (root, 450ms) ├─ Span: validateCart (12ms) ├─ Span: DB INSERT order (38ms) └─ Span: HTTP → payment-svc (395ms) ├─ Span: Stripe charge (380ms) └─ Span: DB INSERT tx (10ms)
Claude Code Tipp: Frage Claude Code nach dem OTel-Datenmodell mit claude "Erkläre den Unterschied zwischen SpanContext und SpanLink in OpenTelemetry" — du bekommst sofort praxisnahe TypeScript-Beispiele.

2. Node.js SDK Setup — @opentelemetry/sdk-node und Auto-Instrumentation

Das offizielle Node.js SDK bündelt alles: SDK-Core, Auto-Instrumentation und OTLP-Exporter. Claude Code generiert das vollständige Setup — inklusive Registrierung aller gängigen Auto-Instrumentierungen für HTTP, Express, gRPC, Prisma und mehr.

SETUP Pakete installieren

# Core SDK + Auto-Instrumentierung + OTLP-Exporter npm install \ @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ @opentelemetry/exporter-trace-otlp-http \ @opentelemetry/exporter-metrics-otlp-http \ @opentelemetry/resources \ @opentelemetry/semantic-conventions

SETUP instrumentation.ts — Einmal initialisieren, überall verfügbar

Diese Datei muss vor allem anderen Code geladen werden — per --require Flag oder als erster Import in server.ts.

import { NodeSDK } from '@opentelemetry/sdk-node'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'; import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; import { Resource } from '@opentelemetry/resources'; import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION, } from '@opentelemetry/semantic-conventions'; const resource = Resource.default().merge( new Resource({ [SEMRESATTRS_SERVICE_NAME]: 'checkout-service', [SEMRESATTRS_SERVICE_VERSION]: process.env.APP_VERSION ?? '0.0.0', 'deployment.environment': process.env.NODE_ENV ?? 'development', }) ); const traceExporter = new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ?? 'http://localhost:4318/v1/traces', }); const metricExporter = new OTLPMetricExporter({ url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT ?? 'http://localhost:4318/v1/metrics', }); const sdk = new NodeSDK({ resource, traceExporter, metricReader: new PeriodicExportingMetricReader({ exporter: metricExporter, exportIntervalMillis: 15_000, // alle 15 Sekunden exportieren }), instrumentations: [ getNodeAutoInstrumentations({ '@opentelemetry/instrumentation-fs': { enabled: false }, // fs ist zu chatty '@opentelemetry/instrumentation-http': { ignoreIncomingRequestHook: (req) => req.url?.startsWith('/health') ?? false, }, }), ], }); sdk.start(); process.on('SIGTERM', () => { sdk .shutdown() .then(() => console.log('OTel SDK sauber beendet')) .catch((err) => console.error('Shutdown-Fehler:', err)) .finally(() => process.exit(0)); });

SETUP package.json — Instrumentation vor dem Server laden

{ "scripts": { "start": "node --require ./dist/instrumentation.js dist/server.js", "dev": "ts-node --require ./src/instrumentation.ts src/server.ts" } }

3. Custom Spans und Attributes

Auto-Instrumentation erfasst HTTP, DB und gRPC automatisch. Für Business-Logik brauchst du Custom Spans — z.B. "Warenkorb validieren", "Preis berechnen", "Fraud-Check". Claude Code erstellt diese Spans typsicher mit vollständigen Attributen.

TRACE tracer.startActiveSpan() — Der empfohlene Weg

import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api'; const tracer = trace.getTracer('checkout-service', '1.0.0'); async function processCheckout( cartId: string, userId: string ): Promise<Order> { return tracer.startActiveSpan( 'checkout.process', { kind: SpanKind.SERVER, attributes: { 'checkout.cart_id': cartId, 'checkout.user_id': userId, 'checkout.source': 'web', }, }, async (span) => { try { const cart = await loadCart(cartId); span.setAttribute('checkout.item_count', cart.items.length); span.setAttribute('checkout.total_eur', cart.totalEur); // Fraud-Check als Kind-Span const isSafe = await tracer.startActiveSpan( 'checkout.fraud_check', async (s) => { const result = await fraudService.check(userId, cart); s.setAttribute('fraud.score', result.score); s.setAttribute('fraud.decision', result.decision); s.end(); return result.decision === 'allow'; } ); if (!isSafe) { span.setStatus({ code: SpanStatusCode.ERROR, message: 'Fraud detected' }); span.setAttribute('checkout.blocked', true); span.end(); throw new Error('Transaction blocked by fraud detection'); } const order = await createOrder(cart, userId); span.setAttribute('checkout.order_id', order.id); span.setStatus({ code: SpanStatusCode.OK }); return order; } catch (err) { span.recordException(err as Error); span.setStatus({ code: SpanStatusCode.ERROR }); throw err; } finally { span.end(); // IMMER im finally-Block! } } ); }

TRACE Span-Events — Zeitstempel ohne eigenen Span

Für wichtige Zwischenschritte innerhalb eines Spans eignen sich Events — sie haben einen Zeitstempel und Attribute, aber keinen eigenen Scope:

span.addEvent('cache.miss', { 'cache.key': cacheKey, 'cache.ttl_remaining': 0, }); // Später im Code: span.addEvent('db.query.start', { 'db.statement': query }); const rows = await db.query(query); span.addEvent('db.query.end', { 'db.rows_returned': rows.length });
Achtung: Span-Attribute niemals mit PII (E-Mail, Passwort, Kreditkartennummer) befüllen. OTel-Backends sind oft langzeit-persistent — im Zweifel hashen oder weglassen.

4. Metrics — Counter, Histogram, Gauge, MeterProvider

Metrics sind aggregierte Zeitreihen — ideal für Dashboards, Alerts und SLO-Tracking. OTel kennt drei Instruments: Counter (immer steigend), Histogram (Verteilung von Werten) und Gauge (aktueller Zustand). Claude Code wählt automatisch das richtige Instrument für deinen Use-Case.

METRIC Counter, Histogram, Gauge — alle drei in einem Service

import { metrics } from '@opentelemetry/api'; const meter = metrics.getMeter('checkout-service', '1.0.0'); // Counter: monoton steigend — für Requests, Errors, Events const checkoutCounter = meter.createCounter('checkout.requests.total', { description: 'Anzahl Checkout-Anfragen', unit: '{requests}', }); // Histogram: Verteilung — für Latenz, Größen, Beträge const latencyHistogram = meter.createHistogram('checkout.duration.ms', { description: 'Checkout-Dauer in Millisekunden', unit: 'ms', advice: { explicitBucketBoundaries: [10, 50, 100, 250, 500, 1000, 2500, 5000], }, }); // Gauge: aktueller Wert — für Queue-Größe, aktive Sessions const activeSessionsGauge = meter.createObservableGauge( 'checkout.sessions.active', { description: 'Aktuell aktive Checkout-Sessions' } ); activeSessionsGauge.addCallback((result) => { result.observe(sessionStore.size()); }); // Im Request-Handler: async function handleCheckout(req: Request, res: Response) { const start = Date.now(); const labels = { payment_method: req.body.paymentMethod, region: req.body.region, }; try { const order = await processCheckout(req.body.cartId, req.user.id); checkoutCounter.add(1, { ...labels, status: 'success' }); res.json({ orderId: order.id }); } catch (err) { checkoutCounter.add(1, { ...labels, status: 'error' }); res.status(500).json({ error: 'Checkout fehlgeschlagen' }); } finally { latencyHistogram.record(Date.now() - start, labels); } }

METRIC Prometheus Exporter — für Grafana-Dashboards

Neben OTLP unterstützt OTel auch Prometheus nativ — ideal wenn Grafana bereits im Stack ist:

import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; import { MeterProvider } from '@opentelemetry/sdk-metrics'; const prometheusExporter = new PrometheusExporter({ port: 9464, // GET /metrics für Prometheus-Scrape preventServerStart: false, }); const meterProvider = new MeterProvider({ readers: [prometheusExporter], resource, }); // In prometheus.yml: // scrape_configs: // - job_name: checkout-service // static_configs: // - targets: ['checkout-service:9464']
Claude Code Tipp: Bitte Claude Code um ein vollständiges Grafana-Dashboard JSON für deine OTel-Metrics — es generiert direkt importierbare Dashboard-Definitionen mit den richtigen PromQL-Queries.

5. Distributed Tracing — Context Propagation, W3C Trace Context, B3 Headers

Distributed Tracing wird erst dann wertvoll, wenn Spans über Servicegrenzen hinweg verbunden sind. OTel löst das mit Context Propagation: Der Trace-Kontext wird als HTTP-Header mitgeschickt — entweder im W3C-Standard (traceparent) oder im älteren B3-Format (Zipkin-Kompatibilität).

DIST W3C Trace Context — der moderne Standard

// W3C traceparent Header-Format: // traceparent: 00-{traceId}-{spanId}-{flags} // Beispiel: traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^ version trace-id (128-bit) parent-span-id sampled

DIST Propagator konfigurieren — W3C + B3 parallel

import { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator, } from '@opentelemetry/core'; import { B3Propagator, B3InjectEncoding } from '@opentelemetry/propagator-b3'; // In NodeSDK-Konfiguration: const sdk = new NodeSDK({ // ...andere Optionen... textMapPropagator: new CompositePropagator({ propagators: [ new W3CTraceContextPropagator(), // modern, default new W3CBaggagePropagator(), // Baggage für Business-Daten new B3Propagator({ // Legacy Zipkin/Istio Kompatibilität injectEncoding: B3InjectEncoding.MULTI_HEADER, }), ], }), });

DIST Manuelle Context-Injection für HTTP-Clients

Auto-Instrumentation übernimmt das für fetch, axios und http. Für eigene HTTP-Clients oder Message-Queue-Nachrichten muss der Kontext manuell eingefügt werden:

import { context, propagation } from '@opentelemetry/api'; async function callPaymentService( payload: PaymentPayload ): Promise<PaymentResult> { const headers: Record<string, string> = { 'Content-Type': 'application/json', }; // Aktuellen Kontext in Headers injizieren propagation.inject(context.active(), headers); // Headers enthalten jetzt: traceparent, tracestate, baggage const response = await fetch('http://payment-service/charge', { method: 'POST', headers, body: JSON.stringify(payload), }); return response.json(); } // Context aus eingehenden Requests extrahieren (für eigene HTTP-Server): function extractContextFromRequest(req: IncomingMessage) { const ctx = propagation.extract(context.active(), req.headers); return ctx; }

DIST Baggage — Business-Kontext über Servicegrenzen

Baggage ist ein Key-Value-Store im Trace-Kontext — für Werte die alle Services kennen müssen (Tenant-ID, Feature-Flags, A/B-Test-Gruppe):

import { propagation, context } from '@opentelemetry/api'; // Baggage setzen (z.B. im Auth-Middleware): const bag = propagation.createBaggage({ 'tenant.id': { value: tenantId }, 'feature.checkout_v2': { value: 'true' }, 'ab.group': { value: user.abGroup }, }); const ctx = propagation.setBaggage(context.active(), bag); // Alle Kind-Spans + alle aufgerufenen Services haben Zugriff: context.with(ctx, async () => { const currentBag = propagation.getBaggage(context.active()); const tenantId = currentBag?.getEntry('tenant.id')?.value; // tenantId ist überall verfügbar, auch 3 Service-Hops tiefer });

6. Backends — Jaeger, Grafana Tempo, Honeycomb, Datadog

Das Beste an OTel: Du schreibst den Code einmal — und wechselst das Backend ohne eine Zeile Instrumentierungs-Code zu ändern. Nur der Exporter-Endpoint ändert sich. Claude Code generiert die passenden Konfigurationen für alle gängigen Backends.

BACKEND Jaeger — kostenlos, selbst gehostet, ideal für Entwicklung

# Jaeger All-in-One mit Docker (OTLP-Port 4317/4318) docker run -d --name jaeger \ -e COLLECTOR_OTLP_ENABLED=true \ -p 16686:16686 \ -p 4317:4317 \ -p 4318:4318 \ jaegertracing/all-in-one:latest # OTel Exporter auf Jaeger zeigen: # OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces # UI: http://localhost:16686

BACKEND Grafana Tempo + Loki + Prometheus — der vollständige Stack

# docker-compose.yml (Ausschnitt) services: tempo: image: grafana/tempo:latest command: -config.file=/etc/tempo.yaml ports: - "4317:4317" # OTLP gRPC - "4318:4318" # OTLP HTTP - "3200:3200" # Tempo API prometheus: image: prom/prometheus:latest ports: ["9090:9090"] grafana: image: grafana/grafana:latest ports: ["3000:3000"] environment: - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor # Datasources: Tempo (Traces) + Prometheus (Metrics) + Loki (Logs) # Grafana verknüpft automatisch Traces <-> Logs via TraceID

BACKEND Honeycomb — managed, developer-friendly, mächtiges Querying

// Honeycomb akzeptiert nativ OTLP — nur API-Key als Header: const traceExporter = new OTLPTraceExporter({ url: 'https://api.honeycomb.io/v1/traces', headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY!, 'x-honeycomb-dataset': 'checkout-service', }, }); // Metrics zu Honeycomb: const metricExporter = new OTLPMetricExporter({ url: 'https://api.honeycomb.io/v1/metrics', headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY!, 'x-honeycomb-dataset': 'checkout-service-metrics', }, });

BACKEND Datadog — Enterprise, OTLP via Datadog Agent

# datadog-agent.yaml (OTLP Receiver aktivieren) otlp_config: receiver: protocols: http: endpoint: 0.0.0.0:4318 grpc: endpoint: 0.0.0.0:4317 // App zeigt auf lokalen Datadog Agent (kein direkter DD-Endpoint nötig): // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://datadog-agent:4318/v1/traces // OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://datadog-agent:4318/v1/metrics // DD_API_KEY wird nur vom Agent gebraucht, nicht von der App

BACKEND Backend-Vergleich auf einen Blick

Backend Hosting OTLP-native Best for
Jaeger Self-hosted Ja Entwicklung, kostenfrei
Grafana Tempo Self- oder Cloud Ja Open-Source Full-Stack
Honeycomb Managed Cloud Ja Developer Experience
Datadog Managed Cloud Via Agent Enterprise, Full-APM
Empfehlung 2026: Starte lokal mit Jaeger (5 Minuten Setup). Für Produktion: Grafana Cloud (Tempo + Loki + Prometheus) ist für kleine Teams oft kosteneffizienter als Datadog — OTel macht den Wechsel jederzeit möglich.

7. Claude Code als OTel-Copilot — Praktischer Workflow

Claude Code versteht das OTel-API vollständig und kennt alle aktuellen SDK-Versionen (Stand 2026). Du beschreibst deine Business-Logik — Claude Code ergänzt die Instrumentierung ohne deine Kernfunktionen zu verändern.

WORKFLOW Typische Claude Code Prompts für OTel

# Bestehende Funktion instrumentieren: claude "Füge OpenTelemetry Custom Spans zu dieser Funktion hinzu. Erfasse: Eingangsparameter als Attribute, Fehler mit recordException, Status OK/ERROR. Nutze tracer.startActiveSpan()." # Metrics für Express-Route: claude "Erstelle OTel Metrics für diese Express-Route: Counter für Requests (mit status/method Labels), Histogram für Response-Zeit, Gauge für aktive Connections." # Komplettes OTel-Setup generieren: claude "Erstelle instrumentation.ts für einen Node.js Service. Backend: Grafana Tempo (OTLP HTTP). Instrumentiere: HTTP, Express, Prisma ORM. Exportiere Metrics via Prometheus auf Port 9464."

WORKFLOW Sampling-Strategie für Produktions-Traffic

In der Produktion willst du nicht jeden Span exportieren — das kostet Geld und Performance. OTel bietet mehrere Sampling-Strategien:

import { ParentBasedSampler, TraceIdRatioBasedSampler, AlwaysOnSampler, } from '@opentelemetry/sdk-trace-base'; const sampler = new ParentBasedSampler({ // Root-Spans: 10% samplen root: new TraceIdRatioBasedSampler(0.1), // Kind-Spans: Entscheidung des Parents respektieren remoteParentSampled: new AlwaysOnSampler(), remoteParentNotSampled: { shouldSample: () => ({ decision: SamplingDecision.NOT_RECORD }), }, }); const sdk = new NodeSDK({ sampler, // ...rest of config }); // Tipp: Errors IMMER samplen, auch wenn Rate-Sampler sagt Nein // → Custom Sampler der bei span.setStatus(ERROR) immer aufzeichnet

TRACE Logs mit Traces korrelieren

OTel-Logs ohne Trace-Korrelation sind schwer nutzbar. Füge TraceID + SpanID in jeden Log-Eintrag ein — dann kannst du von Grafana Loki direkt in Tempo springen:

import { trace, context } from '@opentelemetry/api'; import winston from 'winston'; const otelFormat = winston.format((info) => { const span = trace.getActiveSpan(); if (span) { const spanContext = span.spanContext(); info.trace_id = spanContext.traceId; info.span_id = spanContext.spanId; info.trace_flags = spanContext.traceFlags.toString(16); } return info; }); const logger = winston.createLogger({ format: winston.format.combine( otelFormat(), winston.format.json() ), transports: [new winston.transports.Console()], }); // Log-Output enthält jetzt: // {"level":"info","message":"Order created","trace_id":"4bf92f35...","span_id":"00f067aa..."}

Observability-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges Observability-Stack — OpenTelemetry, Sentry, Grafana und Prometheus für produktionsreife Node.js-Anwendungen. Schritt für Schritt, mit echtem Produktions-Code.

14 Tage kostenlos testen →