How to instrument Python application automatically using OpenTelemetry Framework
Here in this article we will see how we can instrument a Python flask application using OpenTelemetry framework without any modification in the application code. This is an example automatic instrumentation of application using the OpenTelemetry agent.
Test Environment
Fedora 39 workstation
Python 3.x
What is Observability
It is the ability to understand about the internals of the software or system by collecting different type of data and analysing it. The data can be related to the load , request traffic, resource utilization, component tracing. The data that we basically collect to analyze the system state is called telemetry data which includes traces, metrics, logs.
What is OpenTelemetry
Its a vendor neutral open source Observability framework for instrumenting, generating, collecting and exporting telemetry data such as traces, metrics, logs.
What is Automatic Instrumentation
Its a method to collect the telemetry data from application without any modification in the code. Language specific implementation of OpenTelemetry will provide a way to instrument the application in this automated manner. These implementation at the minimum add the OpenTelemetry API and SDK capabilities to the application. They also may add instrumentation libraries and exporter dependencies.
If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.
Procedure
Step1: Create a Python Virtual Environment
As a first step let us create a virtual environment and activate it to install the relevant packages for python Flask application and OpenTelemetry Framework.
admin@fedser:vscodeprojects$ mkdir otel-getting-started
admin@fedser:vscodeprojects$ cd otel-getting-started
admin@fedser:otel-getting-started$ python -m venv venv
admin@fedser:otel-getting-started$ source ./venv/bin/activate
(venv) admin@fedser:otel-getting-started$
Step2: Install Flask
Here we are installed the required packages for python Flask application. They are ‘flask’ and ‘werkzeug’.
(venv) admin@fedser:otel-getting-started$ pip install 'flask<3' 'werkzeug<3'
Step3: Create and Run Flask Application
Now let us create this very basic web application which is a simulation of dice game. Whenever “/rolldice” context is called it provides us with a random number between 1 and 6 as the output. If we pass the “player” argument with his name. It will capture the player name who is rolling the dice, otherwise it will log as anonymous user.
Create Application
(venv) admin@fedser:otel-getting-started$ cat app.py
from random import randint
from flask import Flask, request
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.route("/rolldice")
def roll_dice():
player = request.args.get('player', default = None, type = str)
result = str(roll())
if player:
logger.warn("%s is rolling the dice: %s", player, result)
else:
logger.warn("Anonymous player is rolling the dice: %s", result)
return result
def roll():
return randint(1, 6)
Run Application
(venv) admin@fedser:otel-getting-started$ flask run -p 8080
Validate Application
URL - http://localhost:8080/rolldice?player=alice
Console Log
(venv) admin@fedser:otel-getting-started$ flask run -p 8080
* Debug mode: off
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:8080
INFO:werkzeug:Press CTRL+C to quit
WARNING:app:alice is rolling the dice: 3
INFO:werkzeug:127.0.0.1 - - [09/Dec/2023 12:15:50] "GET /rolldice?player=alice HTTP/1.1" 200 -
Stop the application before proceeding to the next step.
Step4: Instrument Application
Here we’ll use the opentelemetry-instrument agent. Install the opentelemetry-distro package, which contains the OpenTelemetry API, SDK and also the tools opentelemetry-bootstrap and opentelemetry-instrument as shown below. Also we will install opentelemetry-exporter-otlp package which installs supported exporters.
(venv) admin@fedser:otel-getting-started$ pip install opentelemetry-distro opentelemetry-exporter-otlp
The opentelemetry-bootstrap -a install command reads through the list of packages installed in your active site-packages folder, and installs the corresponding instrumentation libraries for these packages.
(venv) admin@fedser:otel-getting-started$ opentelemetry-bootstrap -a install
Step5: Run Instrumented Application
Now its time to run the instrumented application as shown below. Here we are exporting the traces, metrics and logs data to the console from where the flast application is started and also we are giving our application a service name called “dice-server”.
Once the instrumented application is up and running we can try to access the application context using “http://localhost:8080/rolldice?player=alice” from browser. You can try to send multiple requests to the the application. After a while you should see the spans printed in the console, such as the following.
(venv) admin@fedser:otel-getting-started$ opentelemetry-instrument --traces_exporter console --metrics_exporter console --logs_exporter console --service_name dice-server flask run -p 8080
* Debug mode: off
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:8080
INFO:werkzeug:Press CTRL+C to quit
WARNING:app:henry is rolling the dice: 4
INFO:werkzeug:127.0.0.1 - - [09/Dec/2023 12:40:34] "GET /rolldice?player=henry HTTP/1.1" 200 -
{
"name": "/rolldice",
"context": {
"trace_id": "0xcc029b2cbb1b449c7719f935b8fdf63a",
"span_id": "0x50cfb5201728d919",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": null,
"start_time": "2023-12-09T07:10:34.862450Z",
"end_time": "2023-12-09T07:10:34.863636Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "GET",
"http.server_name": "127.0.0.1",
"http.scheme": "http",
"net.host.port": 8080,
"http.host": "127.0.0.1:8080",
"http.target": "/rolldice?player=henry",
"net.peer.ip": "127.0.0.1",
"http.user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"net.peer.port": 58876,
"http.flavor": "1.1",
"http.route": "/rolldice",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.21.0",
"service.name": "dice-server",
"telemetry.auto.version": "0.42b0"
},
"schema_url": ""
}
}
^C
The generated span tracks the lifetime of a request to the /rolldice route. The log line emitted during the request contains the same trace ID and span ID and is exported to the console via the log exporter.
- trace_id – This is an id that’s assigned to a single request, job, or action. Something like each unique user initiated web request will have its own traceid.
- span_id – This tracks a unit of work. Think of a request that consists of multiple steps
Try sending a few requests from multiple browser tabs and reload the pages to send more requests and wait for a little bit or terminate the app and you’ll see metrics in the console output, such as the following.
Metrics Data
{
"resource_metrics": [
{
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.21.0",
"service.name": "dice-server",
"telemetry.auto.version": "0.42b0"
},
"schema_url": ""
},
"scope_metrics": [
{
"scope": {
"name": "opentelemetry.instrumentation.flask",
"version": "0.42b0",
"schema_url": "https://opentelemetry.io/schemas/1.11.0"
},
"metrics": [
{
"name": "http.server.active_requests",
"description": "measures the number of concurrent HTTP requests that are currently in-flight",
"unit": "requests",
"data": {
"data_points": [
{
"attributes": {
"http.method": "GET",
"http.host": "127.0.0.1:8080",
"http.scheme": "http",
"http.flavor": "1.1",
"http.server_name": "127.0.0.1"
},
"start_time_unix_nano": 1702105834862508699,
"time_unix_nano": 1702105841052247437,
"value": 0
}
],
"aggregation_temporality": 2,
"is_monotonic": false
}
},
{
"name": "http.server.duration",
"description": "measures the duration of the inbound HTTP request",
"unit": "ms",
"data": {
"data_points": [
{
"attributes": {
"http.method": "GET",
"http.host": "127.0.0.1:8080",
"http.scheme": "http",
"http.flavor": "1.1",
"http.server_name": "127.0.0.1",
"net.host.port": 8080,
"http.status_code": 200
},
"start_time_unix_nano": 1702105834863724392,
"time_unix_nano": 1702105841052247437,
"count": 1,
"sum": 1,
"bucket_counts": [
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"explicit_bounds": [
0.0,
5.0,
10.0,
25.0,
50.0,
75.0,
100.0,
250.0,
500.0,
750.0,
1000.0,
2500.0,
5000.0,
7500.0,
10000.0
],
"min": 1,
"max": 1
}
],
"aggregation_temporality": 2
}
}
],
"schema_url": "https://opentelemetry.io/schemas/1.11.0"
}
],
"schema_url": ""
}
]
}
Hope you enjoyed reading this article. Thank you..
Leave a Reply
You must be logged in to post a comment.