--- title: Getting Started description: Get telemetry for your app in less than 5 minutes! spelling: cSpell:ignore venv distro rolldice randint spelling: cSpell:ignore rollspan loglevel loggingexporter weight: 10 --- This page will show you how to get started with OpenTelemetry in Python. You will learn how you can instrument a simple application automatically, in such a way that [traces][], [metrics][] and [logs][] are emitted to the console. ## Prerequisites Ensure that you have the following installed locally: - [Python 3](https://www.python.org/) ## Example Application The following example uses a basic [Flask](https://flask.palletsprojects.com/) application. If you are not using Flask, that's ok — you can use OpenTelemetry Python with other web frameworks as well, such as Django and FastAPI. For a complete list of libraries for supported frameworks, see the [registry](/ecosystem/registry/?component=instrumentation&language=python). For more elaborate examples, see [examples](/docs/instrumentation/python/examples/). ## Installation To begin, set up an environment in a new directory: ```shell mkdir otel-getting-started cd otel-getting-started python3 -m venv . source ./bin/activate ``` Now install Flask: ```shell pip install flask ``` ### Create and launch an HTTP Server Create a file `app.py` and add the following code to it: ```python from random import randint from flask import Flask app = Flask(__name__) @app.route("/rolldice") def roll_dice(): return str(do_roll()) def do_roll(): return randint(1, 6) ``` Run the application with the following command and open in your web browser to ensure it is working. ```sh flask run -p 8080 ``` ## Instrumentation Automatic instrumentation will generate telemetry data on your behalf. There are several options you can take, covered in more detail in [Automatic Instrumentation](../automatic/). 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` you will use below. ``` pip install opentelemetry-distro ``` Run the `opentelemetry-bootstrap` command: ```shell opentelemetry-bootstrap -a install ``` This will install Flask instrumentation. ## Run the instrumented app You can now run your instrumented app with `opentelemetry-instrument` and have it print to the console for now: ```shell opentelemetry-instrument \ --traces_exporter console \ --metrics_exporter console \ --logs_exporter console \ flask run -p 8080 ``` Open in your web browser and reload the page a few times. After a while you should see the spans printed in the console, such as the following:
View example output ```json { "name": "/rolldice", "context": { "trace_id": "0xdcd253b9501348b63369d83219da0b14", "span_id": "0x886c05bc23d2250e", "trace_state": "[]" }, "kind": "SpanKind.SERVER", "parent_id": null, "start_time": "2022-04-27T23:53:11.533109Z", "end_time": "2022-04-27T23:53:11.534097Z", "status": { "status_code": "UNSET" }, "attributes": { "http.method": "GET", "http.server_name": "127.0.0.1", "http.scheme": "http", "net.host.port": 5000, "http.host": "localhost:5000", "http.target": "/rolldice", "net.peer.ip": "127.0.0.1", "http.user_agent": "curl/7.68.0", "net.peer.port": 52538, "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.14.0", "telemetry.auto.version": "0.35b0", "service.name": "unknown_service" }, "schema_url": "" } } ```
The generated span tracks the lifetime of a request to the `/rolldice` route. Send a few more requests to the endpoint, and then either wait for a little bit or terminate the app and you'll see metrics in the console output, such as the following:
View example output ```json { "resource_metrics": [ { "resource": { "attributes": { "service.name": "unknown_service", "telemetry.auto.version": "0.34b0", "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.13.0" }, "schema_url": "" }, "schema_url": "", "scope_metrics": [ { "metrics": [ { "data": { "aggregation_temporality": 2, "data_points": [ { "attributes": { "http.flavor": "1.1", "http.host": "localhost:5000", "http.method": "GET", "http.scheme": "http", "http.server_name": "127.0.0.1" }, "start_time_unix_nano": 1666077040061693305, "time_unix_nano": 1666077098181107419, "value": 0 } ], "is_monotonic": false }, "description": "measures the number of concurrent HTTP requests that are currently in-flight", "name": "http.server.active_requests", "unit": "requests" }, { "data": { "aggregation_temporality": 2, "data_points": [ { "attributes": { "http.flavor": "1.1", "http.host": "localhost:5000", "http.method": "GET", "http.scheme": "http", "http.server_name": "127.0.0.1", "http.status_code": 200, "net.host.port": 5000 }, "bucket_counts": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], "count": 1, "explicit_bounds": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 1000 ], "max": 1, "min": 1, "start_time_unix_nano": 1666077040063027610, "sum": 1, "time_unix_nano": 1666077098181107419 } ] }, "description": "measures the duration of the inbound HTTP request", "name": "http.server.duration", "unit": "ms" } ], "schema_url": "", "scope": { "name": "opentelemetry.instrumentation.flask", "schema_url": "", "version": "0.34b0" } } ] } ] } ```
## Add manual instrumentation to automatic instrumentation Automatic instrumentation captures telemetry at the edges of your systems, such as inbound and outbound HTTP requests, but it doesn't capture what's going on in your application. For that you'll need to write some [manual instrumentation](../manual/). Here's how you can easily link up manual instrumentation with automatic instrumentation. ### Traces First, modify `app.py` to include code that initializes a tracer and uses it to create a trace that's a child of the one that's automatically generated: ```python # These are the necessary import declarations from opentelemetry import trace from random import randint from flask import Flask # Acquire a tracer tracer = trace.get_tracer("diceroller.tracer") app = Flask(__name__) @app.route("/rolldice") def roll_dice(): return str(do_roll()) def do_roll(): # This creates a new span that's the child of the current one with tracer.start_as_current_span("do_roll") as rollspan: res = randint(1, 6) rollspan.set_attribute("roll.value", res) return res ``` Now run the app again: ```shell opentelemetry-instrument \ --traces_exporter console \ --metrics_exporter console \ flask run -p 8080 ``` When you send a request to the server, you'll see two spans in the trace emitted to the console, and the one called `do_roll` registers its parent as the automatically created one:
View example output ```json { "name": "do_roll", "context": { "trace_id": "0x48da59d77e13beadd1a961dc8fcaa74e", "span_id": "0x40c38b50bc8da6b7", "trace_state": "[]" }, "kind": "SpanKind.INTERNAL", "parent_id": "0x84f8c5d92970d94f", "start_time": "2022-04-28T00:07:55.892307Z", "end_time": "2022-04-28T00:07:55.892331Z", "status": { "status_code": "UNSET" }, "attributes": { "roll.value": 4 }, "events": [], "links": [], "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.14.0", "telemetry.auto.version": "0.35b0", "service.name": "unknown_service" }, "schema_url": "" } } { "name": "/rolldice", "context": { "trace_id": "0x48da59d77e13beadd1a961dc8fcaa74e", "span_id": "0x84f8c5d92970d94f", "trace_state": "[]" }, "kind": "SpanKind.SERVER", "parent_id": null, "start_time": "2022-04-28T00:07:55.891500Z", "end_time": "2022-04-28T00:07:55.892552Z", "status": { "status_code": "UNSET" }, "attributes": { "http.method": "GET", "http.server_name": "127.0.0.1", "http.scheme": "http", "net.host.port": 5000, "http.host": "localhost:5000", "http.target": "/rolldice", "net.peer.ip": "127.0.0.1", "http.user_agent": "curl/7.68.0", "net.peer.port": 53824, "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.14.0", "telemetry.auto.version": "0.35b0", "service.name": "unknown_service" }, "schema_url": "" } } ```
The `parent_id` of `do_roll` is the same is the `span_id` for `/rolldice`, indicating a parent-child relationship! ### Metrics Now modify `app.py` to include code that initializes a meter and uses it to create a counter instrument which counts the number of rolls for each possible roll value: ```python # These are the necessary import declarations from opentelemetry import trace from opentelemetry import metrics from random import randint from flask import Flask tracer = trace.get_tracer("diceroller.tracer") # Acquire a meter. meter = metrics.get_meter("diceroller.meter") # Now create a counter instrument to make measurements with roll_counter = meter.create_counter( "roll_counter", description="The number of rolls by roll value", ) app = Flask(__name__) @app.route("/rolldice") def roll_dice(): return str(do_roll()) def do_roll(): with tracer.start_as_current_span("do_roll") as rollspan: res = randint(1, 6) rollspan.set_attribute("roll.value", res) # This adds 1 to the counter for the given roll value roll_counter.add(1, {"roll.value": res}) return res ``` Now run the app again: ```shell opentelemetry-instrument \ --traces_exporter console \ --metrics_exporter console \ flask run -p 8080 ``` When you send a request to the server, you'll see the roll counter metric emitted to the console, with separate counts for each roll value:
View example output ```json { "resource_metrics": [ { "resource": { "attributes": { "telemetry.sdk.language": "python", "telemetry.sdk.name": "opentelemetry", "telemetry.sdk.version": "1.12.0rc1", "telemetry.auto.version": "0.31b0", "service.name": "unknown_service" }, "schema_url": "" }, "scope_metrics": [ { "scope": { "name": "app", "version": "", "schema_url": null }, "metrics": [ { "name": "roll_counter", "description": "The number of rolls by roll value", "unit": "", "data": { "data_points": [ { "attributes": { "roll.value": 4 }, "start_time_unix_nano": 1654790325350232600, "time_unix_nano": 1654790332211598800, "value": 3 }, { "attributes": { "roll.value": 6 }, "start_time_unix_nano": 1654790325350232600, "time_unix_nano": 1654790332211598800, "value": 4 }, { "attributes": { "roll.value": 5 }, "start_time_unix_nano": 1654790325350232600, "time_unix_nano": 1654790332211598800, "value": 1 }, { "attributes": { "roll.value": 1 }, "start_time_unix_nano": 1654790325350232600, "time_unix_nano": 1654790332211598800, "value": 2 }, { "attributes": { "roll.value": 3 }, "start_time_unix_nano": 1654790325350232600, "time_unix_nano": 1654790332211598800, "value": 1 } ], "aggregation_temporality": 2, "is_monotonic": true } } ], "schema_url": null } ], "schema_url": "" } ] } ```
## Send telemetry to an OpenTelemetry Collector The [OpenTelemetry Collector](/docs/collector/getting-started/) is a critical component of most production deployments. Some examples of when it's beneficial to use a collector: - A single telemetry sink shared by multiple services, to reduce overhead of switching exporters - Aggregating traces across multiple services, running on multiple hosts - A central place to process traces prior to exporting them to a backend Unless you have just a single service or are experimenting, you'll want to use a collector in production deployments. ### Configure and run a local collector First, save the following collector configuration code to a file in the `/tmp/` directory: ```yaml # /tmp/otel-collector-config.yaml receivers: otlp: protocols: grpc: exporters: logging: loglevel: debug processors: batch: service: pipelines: traces: receivers: [otlp] exporters: [logging] processors: [batch] metrics: receivers: [otlp] exporters: [logging] processors: [batch] ``` Then run the docker command to acquire and run the collector based on this configuration: ```shell docker run -p 4317:4317 \ -v /tmp/otel-collector-config.yaml:/etc/otel-collector-config.yaml \ otel/opentelemetry-collector:latest \ --config=/etc/otel-collector-config.yaml ``` You will now have an collector instance running locally, listening on port 4317. ### Modify the command to export spans and metrics via OTLP The next step is to modify the command to send spans and metrics to the collector via OTLP instead of the console. To do this, install the OTLP exporter package: ``` pip install opentelemetry-exporter-otlp ``` The `opentelemetry-instrument` agent will detect the package you just installed and default to OTLP export when it's run next. ### Run the application Run the application like before, but don't export to the console: ``` opentelemetry-instrument flask run -p 8080 ``` By default, `opentelemetry-instrument` exports traces and metrics over OTLP/gRPC and will send them to `localhost:4317`, which is what the collector is listening on. When you access the `/rolldice` route now, you'll see output in the collector process instead of the flask process, which should look something like this:
View example output ``` 2022-06-09T20:43:39.915Z DEBUG loggingexporter/logging_exporter.go:51 ResourceSpans #0 Resource labels: -> telemetry.sdk.language: STRING(python) -> telemetry.sdk.name: STRING(opentelemetry) -> telemetry.sdk.version: STRING(1.12.0rc1) -> telemetry.auto.version: STRING(0.31b0) -> service.name: STRING(unknown_service) InstrumentationLibrarySpans #0 InstrumentationLibrary app Span #0 Trace ID : 7d4047189ac3d5f96d590f974bbec20a Parent ID : 0b21630539446c31 ID : 4d18cee9463a79ba Name : do_roll Kind : SPAN_KIND_INTERNAL Start time : 2022-06-09 20:43:37.390134089 +0000 UTC End time : 2022-06-09 20:43:37.390327687 +0000 UTC Status code : STATUS_CODE_UNSET Status message : Attributes: -> roll.value: INT(5) InstrumentationLibrarySpans #1 InstrumentationLibrary opentelemetry.instrumentation.flask 0.31b0 Span #0 Trace ID : 7d4047189ac3d5f96d590f974bbec20a Parent ID : ID : 0b21630539446c31 Name : /rolldice Kind : SPAN_KIND_SERVER Start time : 2022-06-09 20:43:37.388733595 +0000 UTC End time : 2022-06-09 20:43:37.390723792 +0000 UTC Status code : STATUS_CODE_UNSET Status message : Attributes: -> http.method: STRING(GET) -> http.server_name: STRING(127.0.0.1) -> http.scheme: STRING(http) -> net.host.port: INT(5000) -> http.host: STRING(localhost:5000) -> http.target: STRING(/rolldice) -> net.peer.ip: STRING(127.0.0.1) -> http.user_agent: STRING(curl/7.82.0) -> net.peer.port: INT(53878) -> http.flavor: STRING(1.1) -> http.route: STRING(/rolldice) -> http.status_code: INT(200) 2022-06-09T20:43:40.025Z INFO loggingexporter/logging_exporter.go:56 MetricsExporter {"#metrics": 1} 2022-06-09T20:43:40.025Z DEBUG loggingexporter/logging_exporter.go:66 ResourceMetrics #0 Resource labels: -> telemetry.sdk.language: STRING(python) -> telemetry.sdk.name: STRING(opentelemetry) -> telemetry.sdk.version: STRING(1.12.0rc1) -> telemetry.auto.version: STRING(0.31b0) -> service.name: STRING(unknown_service) InstrumentationLibraryMetrics #0 InstrumentationLibrary app Metric #0 Descriptor: -> Name: roll_counter -> Description: The number of rolls by roll value -> Unit: -> DataType: Sum -> IsMonotonic: true -> AggregationTemporality: AGGREGATION_TEMPORALITY_CUMULATIVE NumberDataPoints #0 Data point attributes: -> roll.value: INT(5) StartTimestamp: 2022-06-09 20:43:37.390226915 +0000 UTC Timestamp: 2022-06-09 20:43:39.848587966 +0000 UTC Value: 1 ```
## Next steps There are several options available for automatic instrumentation and Python. See [Automatic Instrumentation](../automatic/) to learn about them and how to configure them. There's a lot more to manual instrumentation than just creating a child span. To learn details about initializing manual instrumentation and many more parts of the OpenTelemetry API you can use, see [Manual Instrumentation](../manual/). Finally, there are several options for exporting your telemetry data with OpenTelemetry. To learn how to export your data to a preferred backend, see [Exporters](../exporters/). [traces]: /docs/concepts/signals/traces/ [metrics]: /docs/concepts/signals/metrics/ [logs]: /docs/concepts/signals/logs/