Distributed Tracing Essentials: Following Requests Across Services
How to implement distributed tracing to debug requests across multiple services — from basic concepts to OpenTelemetry instrumentation.
February 19, 2026 · 9 min · 1805 words · Rob Washington
Table of Contents
A request hits your API gateway, bounces through five microservices, touches two databases, and returns an error. Logs say “something failed.” Which service? Which call? What was the payload?
Distributed tracing answers these questions by connecting the dots across service boundaries.
fromopentelemetryimporttracefromopentelemetry.sdk.traceimportTracerProviderfromopentelemetry.sdk.trace.exportimportBatchSpanProcessorfromopentelemetry.exporter.otlp.proto.grpc.trace_exporterimportOTLPSpanExporterfromopentelemetry.instrumentation.flaskimportFlaskInstrumentorfromopentelemetry.instrumentation.requestsimportRequestsInstrumentor# Configure providerprovider=TracerProvider()processor=BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4317"))provider.add_span_processor(processor)trace.set_tracer_provider(provider)# Auto-instrument librariesFlaskInstrumentor().instrument()RequestsInstrumentor().instrument()# Get tracer for manual instrumentationtracer=trace.get_tracer(__name__)
fromopentelemetryimporttracefromopentelemetry.traceimportStatus,StatusCodetracer=trace.get_tracer(__name__)defprocess_order(order_id:str):withtracer.start_as_current_span("process_order")asspan:# Add attributesspan.set_attribute("order.id",order_id)span.set_attribute("order.source","web")try:# Child span for validationwithtracer.start_as_current_span("validate_order"):validate(order_id)# Child span for paymentwithtracer.start_as_current_span("charge_payment")aspayment_span:payment_span.set_attribute("payment.method","credit_card")result=charge_customer(order_id)payment_span.set_attribute("payment.amount",result.amount)# Add event (like a log within the span)span.add_event("Order processed successfully",{"order.total":result.total})exceptPaymentErrorase:span.set_status(Status(StatusCode.ERROR,str(e)))span.record_exception(e)raise
# otel-collector-config.yamlreceivers:otlp:protocols:grpc:endpoint:0.0.0.0:4317http:endpoint:0.0.0.0:4318processors:batch:timeout:1ssend_batch_size:1024# Add service name if missingresource:attributes:- key:environmentvalue:productionaction:upsert# Sample to reduce volumeprobabilistic_sampler:sampling_percentage:10exporters:# Jaegerjaeger:endpoint:jaeger:14250tls:insecure:true# Or Tempootlp/tempo:endpoint:tempo:4317tls:insecure:true# Debug outputlogging:loglevel:debugservice:pipelines:traces:receivers:[otlp]processors:[batch, resource]exporters:[jaeger, logging]
fromopentelemetry.sdk.trace.samplingimportTraceIdRatioBased,ParentBased# Sample 10% of tracessampler=ParentBased(root=TraceIdRatioBased(0.1))provider=TracerProvider(sampler=sampler)
importjsonfromopentelemetryimporttracefromopentelemetry.propagateimportinject,extractdefenqueue_job(job_data):"""Producer: inject trace context into message"""carrier={}inject(carrier)message={"data":job_data,"trace_context":carrier}queue.publish(json.dumps(message))defprocess_job(message):"""Consumer: extract and continue trace"""data=json.loads(message)context=extract(data.get("trace_context",{}))withtracer.start_as_current_span("process_job",context=context,kind=SpanKind.CONSUMER):do_work(data["data"])
fromopentelemetry.instrumentation.sqlalchemyimportSQLAlchemyInstrumentorSQLAlchemyInstrumentor().instrument(engine=engine,enable_commenter=True,# Adds trace ID to SQL comments)# Queries now include:# SELECT * FROM users /* traceparent='00-abc...' */
Sample appropriately — 100% in dev, tail-sample in prod
Connect logs to traces — Include trace ID in log messages
Set up alerting — Error rate, latency percentiles by trace
Document trace queries — Share common debugging patterns
Distributed tracing turns “something’s broken” into “line 47 of payment-service timed out waiting for inventory-service, which was blocked on a database lock.” That’s the difference between debugging for hours and debugging for minutes.
Instrument everything. Sample wisely. Follow the breadcrumbs.
📬 Get the Newsletter
Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.