OpenTelemetry auto-instrumentation can feel like magic, but the real trick is that it doesn’t actually add code; it hijacks existing code at runtime.

Let’s see it in action. Imagine a simple Python Flask app:

from flask import Flask
import requests

app = Flask(__name__)

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

@app.route('/fetch')
def fetch_data():
    try:
        response = requests.get('http://httpbin.org/get', timeout=5)
        response.raise_for_status() # Raise an exception for bad status codes
        return response.json()
    except requests.exceptions.RequestException as e:
        return str(e), 500

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

Without any OpenTelemetry libraries directly imported or called, we can make it send traces. First, install the necessary packages:

pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-flask opentelemetry-instrumentation-requests

Now, run the application with the OpenTelemetry Python SDK environment variables set. This tells the SDK which instrumentation packages to load and where to send the data:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" # Or your OTLP collector endpoint
export OTEL_SERVICE_NAME="my-flask-app"
python -m opentelemetry_instrumentor --action=instrument your_app.py

When you hit http://localhost:5000/ in your browser, and then http://localhost:5000/fetch, you’ll see requests flowing into your OTLP collector. The first request to / generates a trace for the Flask handler. The second request to /fetch generates a trace that includes a span for the Flask handler and a child span for the requests.get call, all without a single trace.start_span() line in your code.

The core problem auto-instrumentation solves is the manual overhead of adding tracing code to every relevant library call. Historically, developers had to wrap every external HTTP request, database query, or message queue interaction with start_span() and end_span(). This is tedious, error-prone, and often forgotten, leading to incomplete visibility. Auto-instrumentation injects these span-creation and -ending mechanisms into common libraries and frameworks at runtime, typically through class patching or method decoration, allowing you to get distributed traces out-of-the-box.

Internally, these auto-instrumentation packages work by using Python’s powerful introspection and modification capabilities. For libraries like Flask or requests, they hook into the library’s execution flow. For Flask, they might use werkzeug’s request/response lifecycle hooks to create a span when a request comes in and end it when a response is sent. For requests, they intercept the request function (or similar internal methods) to create a span before the HTTP call is made and end it upon receiving a response or encountering an error. The OpenTelemetry SDK then collects these spans and exports them.

The exact levers you control are primarily through environment variables. OTEL_EXPORTER_OTLP_ENDPOINT directs your traces. OTEL_SERVICE_NAME identifies your application. Crucially, you enable or disable specific instrumentations by setting environment variables like OTEL_INSTRUMENTATION_REQUESTS_ENABLED=true (though often they are enabled by default if the package is installed). You can also configure sampling rates, although this is less common for auto-instrumentation itself and more a function of the exporter or collector.

What most people don’t realize is how deeply these hooks can go. For instance, the opentelemetry-instrumentation-sqlalchemy package doesn’t just trace the execute call. It can also trace the connection acquisition and release if the engine is configured appropriately, providing a more granular view of database interaction costs. It achieves this by wrapping the Engine and Connection objects themselves, ensuring that any operation performed through them is captured.

The next step you’ll likely encounter is configuring sampling to manage the volume of trace data sent to your backend.

Want structured learning?

Take the full Opentelemetry course →