This guide demonstrates how to instrument your Python applications with OpenTelemetry and send traces to the TrueFoundry backend. We’ll cover installing the required packages, initializing the tracer, automatic HTTP instrumentation, adding custom attributes and spans, and configuring sampling and debugging options. By the end, you’ll have a clear blueprint for integrating OpenTelemetry tracing into your Python services.

Installation

To start, install the OpenTelemetry SDK and necessary instrumentation packages:

pip install opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-http \
  opentelemetry-instrumentation-flask \
  opentelemetry-instrumentation-requests \
  flask

Initializing OpenTelemetry

Next, initialize the OpenTelemetry SDK in your application. This involves setting up a Tracer Provider (which manages tracers and spans) and OTLP exporter to send the traces to TrueFoundry Backend.

import os
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

def setup_otel_sdk():
    """Setup OpenTelemetry SDK with TrueFoundry configuration."""
    
    # Set environment variables for OTLP exporter
    os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "<enter_your_api_endpoint>"
    os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Bearer {enter_your_api_key},TFY-Tracing-Project={enter_your_tracing_project}"
        
    # Create tracer provider
    provider = TracerProvider()
    processor = BatchSpanProcessor(otlp_exporter)
    provider.add_span_processor(processor)
    
    # Set global trace provider
    trace.set_tracer_provider(provider)

    # Set global text map propagator for context propagation. Remove this line if you don't want to respect incoming request trace context
    set_global_textmap(TraceContextTextMapPropagator())

    
    return provider

Automatic HTTP Instrumentation

Now that the OpenTelemetry SDK is set up, let’s instrument the Flask HTTP server to automatically trace incoming requests. OpenTelemetry provides middleware for popular frameworks; for Flask, we use the FlaskInstrumentor.

At this point, all incoming HTTP requests are being traced automatically.

from flask import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor

app = Flask(__name__)

# Initialize OpenTelemetry
setup_otel_sdk()

# Instrument Flask
FlaskInstrumentor().instrument_app(app)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(debug=True, port=8080)

Context propagation for outgoing requests

To fully benefit from distributed tracing, you should also propagate trace context in your outgoing HTTP requests. This helps downstream services recognize that their requests are part of a larger distributed trace.

Configure your HTTP client to automatically inject trace context into outgoing requests using OpenTelemetry’s requests instrumentation:

This functionality is not incorporated into the Complete Application example to reduce complexity

import requests
from opentelemetry.instrumentation.requests import RequestsInstrumentor

# Instrument requests library
RequestsInstrumentor().instrument()

def make_request():
    # This request will automatically include trace context
    response = requests.get("http://localhost:8081")
    return response.text

Adding Attributes to Spans

Automatic instrumentation captures basic request information, but you can add custom data to your traces using attributes. Attributes are key-value pairs that provide additional context about your operations. For example, in order service, you might add order.id to make traces more useful.

from opentelemetry import trace

def handle_request(order_id: str):
    # Get current span from context
    current_span = trace.get_current_span()

    # Add attributes to the current span
    current_span.set_attribute("order.id", order_id)

Creating Custom Spans

Automatic instrumentation captures HTTP requests and external calls, but it doesn’t track your application’s internal logic. For important operations, you can manually create spans to trace specific parts of your code. A span represents a unit of work, and creating sub-spans helps you see detailed timing and context for key processes.

For example, if a request triggers a complex function or external call that isn’t automatically captured, you can create a span to trace that specific operation. Manual instrumentation fills these gaps by letting you track what happens inside your application, not just at the edges.

from opentelemetry import trace

def get_order(order_id: str):
    db_tracer = trace.get_tracer("order-server-db")
    
    # Create a new span
    with db_tracer.start_as_current_span("db.query") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("order.items", ["item1", "item2", "item3"])
        return f"order.id: {order_id}, items: ['item1', 'item2', 'item3']"

Complete Application Example

Below is a comprehensive example that demonstrates all the OpenTelemetry concepts we’ve covered.

This application creates an order service HTTP server that sets up OpenTelemetry tracing with proper configuration, automatically instruments HTTP requests using FlaskInstrumentor, creates custom spans for database operations, and adds custom attributes to provide order-specific context.

import os
from flask import Flask
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.propagate import set_global_textmap
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

# Global tracer
db_tracer = trace.get_tracer("order-server-db")

def setup_otel_sdk():
    """Setup OpenTelemetry SDK with TrueFoundry configuration."""
    
    # Set environment variables for OTLP exporter
    os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "<enter_your_api_endpoint>"
    os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Bearer {enter_your_api_key},TFY-Tracing-Project={enter_your_tracing_project}"

    # Create OTLP exporter
    otlp_exporter = OTLPSpanExporter()
        
    # Create tracer provider
    provider = TracerProvider()
    
    # Add span processors
    provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
    
    # Set global tracer provider
    trace.set_tracer_provider(provider)
        
    # Set global text map propagator for context propagation. Remove this line if you don't want to respect incoming request trace context
    set_global_textmap(TraceContextTextMapPropagator())
    
    return provider

def mock_fetch_order(order_id: str) -> str:
    """Mock database operation with custom span."""
    with db_tracer.start_as_current_span("db.query") as span:
        span.set_attribute("order.id", order_id)
        span.set_attribute("order.items", ["item1", "item2", "item3"])
        return f"order.id: {order_id}, items: ['item1', 'item2', 'item3']"

app = Flask(__name__)

# Initialize OpenTelemetry
setup_otel_sdk()

# Instrument Flask
FlaskInstrumentor().instrument_app(app)

@app.route('/order-server/orders/<order_id>', methods=['GET'])
def get_order(order_id):
    """Get order endpoint with tracing."""

    # Update existing span attributes
    current_span = trace.get_current_span()
    current_span.set_attribute("order.id", order_id)
    
    # Fetch order with custom span
    order = mock_fetch_order(order_id)
    
    return order

if __name__ == '__main__':
    app.run(debug=True, port=8080)

Run your application and view logged trace

Run the application and make a request to test the tracing:

curl http://localhost:8080/order-server/orders/123

Advanced Configuration

Sampling

Tracing sampling is a crucial technique for managing the volume of trace data in production environments. By default, OpenTelemetry Python traces every request, which works well for debugging or development but can become expensive and noisy in high-traffic production systems.

Sampling helps in several ways: it reduces noise in traces, helping you focus on important traces while maintaining visibility into your system. It also helps with cost management in terms of storage, processing, and network bandwidth, making it essential for production deployments.

Sampling Strategies

OpenTelemetry supports several built-in samplers, but in practice, two cover most use cases:

1. TraceIdRatioBased Sampler

Samples a fixed percentage of root traces. This sampler makes sampling decisions independently for each trace.

from opentelemetry.sdk.trace.sampling import TraceIdRatioBased

def setup_otel_sdk():
    # Configure sampling for production
    sampler = TraceIdRatioBased(0.1)  # Sample 10% of traces
    
    provider = TracerProvider(
        sampler=sampler
    )
    
    # ... rest of initialization

Pros: Simple to configure, predictable sampling rate, deterministic behavior

Cons: May create partial traces if child spans are sampled differently, especially when spans are spread across multiple microservices or services with different sampling configurations.

2. ParentBased Sampler (Recommended)

Samples a fixed percentage of root traces and ensures that child spans follow the parent’s sampling decision, maintaining complete trace integrity.

from opentelemetry.sdk.trace.sampling import TraceIdRatioBased, ParentBased

def setup_otel_sdk():
    # Configure sampling for production
    sampler = ParentBased(TraceIdRatioBased(0.1))
    
    provider = TracerProvider(
        sampler=sampler
    )
    
    # ... rest of initialization

Pros: Maintains trace integrity, prevents partial traces, ensures complete trace visibility when sampled

Cons: Slightly more complex configuration, but worth the additional setup for production environments

Troubleshooting

Partial Traces

If you see partial traces (missing spans in the middle of a trace), ensure you’re using ParentBased sampler:

# ❌ May create partial traces across services
sampler = TraceIdRatioBased(0.1)

# ✅ Maintains trace integrity across all services
sampler = ParentBased(TraceIdRatioBased(0.1))

Too Much Data

If you’re still collecting too much data or experiencing high costs, reduce the sampling rate:

# Reduce from 10% to 5% for high-traffic environments
sampler = ParentBased(TraceIdRatioBased(0.05))

# For very high traffic, consider 1% sampling
sampler = ParentBased(TraceIdRatioBased(0.01))

Too Little Data

If you need more visibility for debugging or monitoring, increase the sampling rate:

# Increase from 10% to 25% for better visibility
sampler = ParentBased(TraceIdRatioBased(0.25))

# For critical debugging, consider 50% or higher
sampler = ParentBased(TraceIdRatioBased(0.5))

Local Debugging

For local development and debugging, you can use the console exporter to see traces in your terminal:

from opentelemetry.sdk.trace.export import ConsoleSpanExporter

def setup_otel_sdk():
    # OTLP exporter for TrueFoundry
    otlp_exporter = OTLPSpanExporter()
    
    # Console exporter for local debugging
    console_exporter = ConsoleSpanExporter()
    
    provider = TracerProvider(resource=resource)
    provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
    provider.add_span_processor(BatchSpanProcessor(console_exporter))
    
    trace.set_tracer_provider(provider)

Using Instrumentation Libraries

OpenTelemetry provides instrumentation libraries for many popular frameworks and libraries in the Python ecosystem. These are drop-in packages that automatically generate spans and metrics for operations in those libraries, so you don’t have to instrument everything manually. When you are using third-party libraries or frameworks, you should take advantage of these to save time and ensure consistency.

For example, if you are using a web framework like Django, there is an official instrumentation package opentelemetry-instrumentation-django that can create spans for each HTTP request handled by Django.

OpenTelemetry’s Python Contrib repository contains many such instrumentation packages for popular libraries.