OpenTelemetry Cheat Sheet
Vendor-neutral observability instrumentation covering traces, metrics, logs, the Collector pipeline, and SDK setup across languages.
Node.js Auto-Instrumentation
Bootstrap traces and metrics for an app with zero manual span code.
// tracing.js — imported before any other moduleconst { NodeSDK } = require('@opentelemetry/sdk-node')const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc')const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: 'http://localhost:4317' }), instrumentations: [getNodeAutoInstrumentations()], serviceName: 'checkout-service',})sdk.start()
Manual Span Instrumentation
Wrap custom business logic in a span when auto-instrumentation isn't enough.
from opentelemetry import tracetracer = trace.get_tracer("checkout.service")def process_order(order_id: str): with tracer.start_as_current_span("process_order") as span: span.set_attribute("order.id", order_id) span.add_event("inventory_checked") try: charge_payment(order_id) except Exception as e: span.record_exception(e) span.set_status(trace.StatusCode.ERROR, str(e)) raise
Custom Metrics
Record a counter and a histogram alongside auto-collected runtime metrics.
from opentelemetry import metricsmeter = metrics.get_meter("checkout.service")orders_counter = meter.create_counter( "orders.processed", unit="1", description="Total orders processed")latency_histogram = meter.create_histogram( "order.processing.duration", unit="ms")orders_counter.add(1, {"status": "success"})latency_histogram.record(142.3, {"payment_provider": "stripe"})
OpenTelemetry Collector Pipeline
Receive OTLP data, batch/process it, and export to multiple backends.
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318processors: batch: {} memory_limiter: limit_mib: 512exporters: otlp/jaeger: endpoint: jaeger-collector:4317 prometheus: endpoint: 0.0.0.0:8889service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlp/jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus]
Core Signals & Terms
The vocabulary of the OTel spec.
- Span- a single timed operation with attributes, events, and a parent/child relationship
- Trace- a tree of spans representing one end-to-end request across services
- Context Propagation- passing trace IDs across process/service boundaries (e.g. W3C traceparent header)
- Resource- metadata identifying the entity producing telemetry (service.name, host, k8s.pod.name)
- OTLP- OpenTelemetry Protocol, the standard wire format between SDKs, Collector, and backends
- Sampler- decides which traces to keep (e.g. ParentBased(TraceIdRatioBased) for head sampling)
Manual Context Propagation Across HTTP
Inject and extract trace context by hand when auto-instrumentation can't see a custom transport (e.g. a message queue or RPC framework).
from opentelemetry import trace, propagatefrom opentelemetry.propagate import inject, extracttracer = trace.get_tracer("checkout.service")# producer side: inject current context into outgoing headersdef publish(message: dict): headers = {} inject(headers) # writes traceparent/tracestate queue.send(message, headers=headers)# consumer side: extract and continue the tracedef on_message(message: dict, headers: dict): ctx = extract(headers) with tracer.start_as_current_span("process_message", context=ctx): handle(message)
Tail Sampling in the Collector
Keep 100% of error/slow traces but only a fraction of routine ones, decided after the full trace is buffered.
processors: tail_sampling: decision_wait: 10s num_traces: 100000 policies: - name: errors type: status_code status_code: { status_codes: [ERROR] } - name: slow-requests type: latency latency: { threshold_ms: 500 } - name: baseline-sample type: probabilistic probabilistic: { sampling_percentage: 5 }service: pipelines: traces: receivers: [otlp] processors: [tail_sampling, batch] exporters: [otlp/jaeger]
Logs SDK Correlated with Active Trace
Emit structured logs that automatically carry trace_id/span_id so a log line links back to its trace in the backend.
import loggingfrom opentelemetry.sdk._logs import LoggerProvider, LoggingHandlerfrom opentelemetry.sdk._logs.export import BatchLogRecordProcessorfrom opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporterprovider = LoggerProvider()provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))handler = LoggingHandler(level=logging.INFO, logger_provider=provider)logging.getLogger().addHandler(handler)# any log emitted inside an active span is auto-tagged with trace_id/span_idlogging.getLogger("checkout").info("charge succeeded", extra={"order_id": "o-123"})
Baggage API for Cross-Service Context
Propagate arbitrary key/value business context (not just trace IDs) across service boundaries alongside the trace.
const { propagation, context } = require('@opentelemetry/api')// set baggage before making a downstream callconst baggage = propagation.createBaggage({ 'tenant.id': { value: 'acme-corp' }, 'feature.flag.checkout_v2': { value: 'true' },})const ctxWithBaggage = propagation.setBaggage(context.active(), baggage)context.with(ctxWithBaggage, () => { makeDownstreamRequest() // W3C baggage header goes out automatically})// downstream: read it backconst entry = propagation.getBaggage(context.active())?.getEntry('tenant.id')
Terms Past the Beginner Vocabulary
Concepts you'll need once you're tuning sampling, correlating signals, or debugging cardinality.
- Span Link- connects a span to another (potentially unrelated-trace) span, e.g. linking a batch job span to each of the individual events it processed
- Exemplar- a specific trace ID attached to a metric data point, letting you jump from a Prometheus histogram bucket straight to a representative trace
- Semantic Conventions- the spec's standardized attribute names (http.request.method, db.system) so backends can build dashboards that work across any instrumented service
- Resource Detector- SDK component that auto-populates Resource attributes like cloud.region or k8s.pod.name from the runtime environment
- Head vs Tail Sampling- head sampling decides per-request before the trace completes (cheap, may drop the interesting trace); tail sampling decides after buffering the whole trace (expensive, precise)
- SpanProcessor: Simple vs Batch- SimpleSpanProcessor exports synchronously per span (useful for debugging, terrible for throughput); BatchSpanProcessor is the production default
Deploy the Collector as a separate service (not just SDK-to-backend direct export) as soon as you have more than one backend or language — it centralizes sampling, PII redaction, and retry/batching logic so you're not reimplementing them in every service's SDK config.