Using OTEL SDK - Python
In this tutorial, we will show you how to instrument a Python Flask app with OpenTelemetry and send traces to Quickwit. This tutorial was inspired by the Python OpenTelemetry documentation, huge thanks to the OpenTelemetry team!
Prerequisites
- Python3 installed
- Docker installed
Start a Quickwit instance
Install Quickwit and start a Quickwit instance:
./quickwit run
Start Jaeger UI
Let's start a Jaeger UI instance with docker. Here we need to inform jaeger that it should use quickwit as its backend.
Due to some idiosyncrasy associated with networking with containers, we will have to use a different approach on MacOS & Windows on one side, and Linux on the other side.
MacOS & Windows
We can rely on host.docker.internal
to get the docker bridge ip address, pointing to our quickwit server.
docker run --rm --name jaeger-qw \
-e SPAN_STORAGE_TYPE=grpc-plugin \
-e GRPC_STORAGE_SERVER=host.docker.internal:7281 \
-p 16686:16686 \
jaegertracing/jaeger-query:latest
Linux
By default, quickwit is listening to 127.0.0.1
, and will not respond to request directed
to the docker bridge (172.17.0.1
). There are different ways to solve this problem.
The easiest is probably to use host network mode.
docker run --rm --name jaeger-qw --network=host \
-e SPAN_STORAGE_TYPE=grpc-plugin \
-e GRPC_STORAGE_SERVER=127.0.0.1:7281 \
-p 16686:16686 \
jaegertracing/jaeger-query:latest
Run a simple Flask app
We will start a flask application that is doing three things on each HTTP call http://localhost:5000/process-ip
:
- Fetching an IP address from https://httpbin.org/ip.
- Parsing it and fake processing it with a random sleep.
- Displaying it with a random sleep.
Let's first install the dependencies:
pip install flask
pip install opentelemetry-distro
pip install opentelemetry-exporter-otlp
The opentelemetry-distro package installs the API, SDK, and the opentelemetry-bootstrap and opentelemetry-instrument tools that you’ll use.
Here is the code of our app:
import random
import time
import requests
from flask import Flask
app = Flask(__name__)
@app.route("/process-ip")
def process_ip():
body = fetch()
ip = parse(body)
display(ip)
return ip
def fetch():
resp = requests.get('https://httpbin.org/ip')
body = resp.json()
return body
def parse(body):
# Sleep for a random amount of time to make the span more visible.
secs = random.randint(1, 100) / 1000
time.sleep(secs)
return body["origin"]
def display(ip):
# Sleep for a random amount of time to make the span more visible.
secs = random.randint(1, 100) / 1000
time.sleep(secs)
message = f"Your IP address is `{ip}`."
print(message)
if __name__ == "__main__":
app.run(port=5000)
Auto-instrumentation
OpenTelemetry provides a tool called opentelemetry-bootstrap
that automatically instruments your Python application.
opentelemetry-bootstrap -a install
And that's it, we are now ready to run the app:
# We don't need metrics.
OTEL_METRICS_EXPORTER=none \
OTEL_TRACES_EXPORTER=console \
OTEL_SERVICE_NAME=my_app \
python my_app.py
By hitting http://localhost:5000/process-ip you should see the corresponding trace in the console.
This is nice but it would be even better if we could have the time passed in each steps, get the status code of the HTTP request, and the content type of the response. Let's do that by manually instrumentating our app!
Manual instrumentation
import random
import time
import requests
from flask import Flask
from opentelemetry import trace
# Creates a tracer from the global tracer provider
tracer = trace.get_tracer(__name__)
app = Flask(__name__)
@app.route("/process-ip")
@tracer.start_as_current_span("process_ip")
def process_ip():
body = fetch()
ip = parse(body)
display(ip)
return ip
@tracer.start_as_current_span("fetch")
def fetch():
resp = requests.get('https://httpbin.org/ip')
body = resp.json()
headers = resp.headers
current_span = trace.get_current_span()
current_span.set_attribute("status_code", resp.status_code)
current_span.set_attribute("content_type", headers["Content-Type"])
current_span.set_attribute("content_length", headers["Content-Length"])
return body
@tracer.start_as_current_span("parse")
def parse(body):
# Sleep for a random amount of time to make the span more visible.
secs = random.randint(1, 100) / 1000
time.sleep(secs)
return body["origin"]
@tracer.start_as_current_span("display")
def display(ip):
# Sleep for a random amount of time to make the span more visible.
secs = random.randint(1, 100) / 1000
time.sleep(secs)
message = f"Your IP address is `{ip}`."
print(message)
current_span = trace.get_current_span()
current_span.add_event(message)
if __name__ == "__main__":
app.run(port=5000)
We can now start the new instrumented app:
OTEL_METRICS_EXPORTER=none \
OTEL_TRACES_EXPORTER=console \
OTEL_SERVICE_NAME=my_app \
opentelemetry-instrument python my_instrumented_app.py
If you hit again http://localhost:5000/process-ip, you should see new spans with name fetch
, parse
, and display
and with the corresponding custom attributes!
Sending traces to Quickwit
To send traces to Quickwit, we need to use the OTLP exporter. This is a simple as this:
OTEL_METRICS_EXPORTER=none \ # We don't need metrics
OTEL_SERVICE_NAME=my_app \
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:7281 \
opentelemetry-instrument python my_instrumented_app.py
Now, if you hit http://localhost:5000/process-ip, traces will be send to Quickwit, you just need to wait around 30 seconds before they are indexed. It's time for a coffee break!
30 seconds has passed, let's query the traces from our service:
curl -XPOST http://localhost:7280/api/v1/otel-trace-v0/search -H 'Content-Type: application/json' -d '{
"query": "resource_attributes.service.name:my_app"
}'
And then open the Jaeger UI localhost:16686 and play with it, you have now a Jaeger UI powered by a Quickwit storage backend!
Sending traces to your OpenTelemetry collector
Start a collector as described in the OpenTelemetry collector tutorial and execute the following command:
OTEL_METRICS_EXPORTER=none \ # We don't need metrics
OTEL_SERVICE_NAME=my_app \
opentelemetry-instrument python instrumented_app.py
Traces will be sent to your collector, and then to Quickwit.
Wrap up
In this tutorial, we have seen how to instrument a Python application with OpenTelemetry and send traces to Quickwit. We have also seen how to use the Jaeger UI to analyze traces.
All the code snippets in our tutorial repository.
Please let us know what you think about this tutorial, and if you have any questions, feel free to reach out to us on Discord or Twitter.