---
title: "Instrument a Python application | OpenTelemetry documentation"
description: "Instrument a Python application with OpenTelemetry for Application Observability."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Instrument a Python application

If you need process-level telemetry for Python, follow this guide to set up the [upstream OpenTelemetry SDK for Python](https://opentelemetry.io/docs/languages/python/) for Application Observability.

## Recommended setup for Grafana Cloud

Grafana Labs recommends that you set up OpenTelemetry components, including instrumentation and an OpenTelemetry Collector distribution, using one of the [Grafana Cloud setup guides](/docs/opentelemetry/grafana-cloud/).

These opinionated guides make it easy to get started. They include all the binaries, configuration, and connection parameters you need to set up OpenTelemetry for [Grafana Cloud](/products/cloud/).

## Install the SDK

Before you begin, ensure you have a Python 3 development environment and a Python application to instrument.

Run the following command in your project folder:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
pip install "opentelemetry-distro[otlp]"
opentelemetry-bootstrap -a install
```

## Global Interpreter Lock

The OpenTelemetry Python SDK uses the Global Interpreter Lock (GIL), which can cause performance issues with web servers that spawn multiple processes to serve requests in parallel.

To address this, register a post fork hook that runs after each worker process is forked.

The following code sample shows a post fork hook for Gunicorn:

Python ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```python
import logging
from uuid import uuid4

from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
    OTLPLogExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
    OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
    OTLPSpanExporter,
)

# support for logs is currently experimental
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.resources import SERVICE_INSTANCE_ID
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# your gunicorn config here
# bind = "127.0.0.1:8000"

collector_endpoint = "http://localhost:4317"

def post_fork(server, worker):
    server.log.info("Worker spawned (pid: %s)", worker.pid)

    resource = Resource.create(
        attributes={
            # each worker needs a unique service.instance.id to distinguish the created metrics in prometheus
            SERVICE_INSTANCE_ID: str(uuid4()),
            "worker": worker.pid,
        }
    )

    tracer_provider = TracerProvider(resource=resource)
    tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=collector_endpoint)))
    trace.set_tracer_provider(tracer_provider)

    metrics.set_meter_provider(
        MeterProvider(
            resource=resource,
            metric_readers=[(PeriodicExportingMetricReader(
                OTLPMetricExporter(endpoint=collector_endpoint)
            ))],
        )
    )

    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter(endpoint=collector_endpoint)))
    logging.getLogger().addHandler(LoggingHandler(level=logging.NOTSET, logger_provider=logger_provider))
```

Run the Gunicorn script using the following command: `gunicorn app -c gunicorn.conf.py`.

> Note
> 
> To register a post fork hook for other web servers, such as uWSGI, add the `@postfork` decorator to your hook function.

## Run your application

Run your application with the following command:

sh ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sh
OTEL_RESOURCE_ATTRIBUTES="service.name=<name>,service.namespace=<namespace>,deployment.environment=<environment>" \
OTEL_EXPORTER_OTLP_ENDPOINT=<endpoint> \
OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
opentelemetry-instrument <your_run_command>
```

Replace variables in `<...>` with values specific to your application and backend. For more details, see the [OpenTelemetry service attributes](https://opentelemetry.io/docs/specs/semconv/attributes-registry/service/#service-attributes) documentation.

- `<name>`: your service name, for example, `shoppingcart`
- `<namespace>`: a string to group related services, such as `ecommerce` for a team that manages several services
- `<environment>`: the deployment environment, for example, the `production` deployment tier

Some backends, such as Grafana Cloud, also require you to set authentication headers in the `OTEL_EXPORTER_OTLP_HEADERS` environment variable.

## Example application

See the [Rolldice service](https://github.com/grafana/docker-otel-lgtm/?tab=readme-ov-file#run-example-apps-in-different-languages) for a complete example setup.

## Resources

1. [OpenTelemetry Distro OpenTelemetry documentation](https://opentelemetry.io/docs/languages/python/distro/)
2. [opentelemetry-distro on pypi](https://pypi.org/project/opentelemetry-distro/)
3. [opentelemetry-python-contrib on GitHub](https://github.com/open-telemetry/opentelemetry-python-contrib)
