This tutorial shows simple examples of instrumenting Python applications for metrics and traces using OpenTelemetry and sending them to Coralogix. While the examples utilize a Flask web framework, it is also possible to instrument using other frameworks such as [Tornado](https://www.tornadoweb.org/en/stable/), [Django](https://www.djangoproject.com/), and [more](https://opentelemetry.io/ecosystem/registry/?language=python&component=instrumentation).

## Overview

OpenTelemetry supports [auto](https://opentelemetry.io/docs/instrumentation/python/automatic/), [programmatic](https://opentelemetry.io/docs/languages/python/automatic/example/#programmatically-instrumented-server), and [manual](https://opentelemetry.io/docs/instrumentation/python/manual/) instrumentation for Python applications. While auto instrumentation is the easiest way to instrument an application for traces, it significantly reduces your level of control. For instance, it denies you the ability to explicitly define relationships between functions or effectively propagate context to other applications.

This guide provides examples of **auto**, **programmatic**, and **manual** instrumentation.

## Prerequisites

- Your Coralogix [Send-Your-Data API Key](https://coralogix.com/docs/user-guides/account-management/api-keys/send-your-data-api-key/index.md) and [domain](https://coralogix.com/docs/user-guides/account-management/account-settings/coralogix-domain/index.md)
- Python 3.9+

### Initial setup and dependencies installation for Flask application

Prerequisite steps for this integration include setting up a Python virtual environment, creating a Python file, and installing the required packages for a Flask application.

**STEP 1**. Set up a Python virtual environment.

```bash
# create python virtual environment
python -m venv env

# activate python environment
source env/bin/active

# create app.py
touch app.py
```

**STEP 2**. Add a requirements.txt file and install dependencies.

```bash
flask==3.0.3
opentelemetry-distro
opentelemetry-exporter-otlp
opentelemetry-instrumentation
opentelemetry-instrumentation-flask
coralogix-opentelemetry==0.1.3
```

```bash
pip install -r requirements.txt
```

## **Auto Instrumentation**

Auto instrumentation works by running your application within another wrapper application provided by OpenTelemetry. This wrapper handles collecting traces from all compatible parts of your application.

### **Traces**

Example with a simple Flask app ‘[app.py](http://app.py/)’.

```py
from random import randint
from flask import Flask

app = Flask(__name__)

@app.route("/roll")
def roll_dice():
    return str(do_roll())

def do_roll():
    res = randint(1, 6)
    return f"you've rolled {res}"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)
```

**STEP 1.** You can now run the application using the `opentelemetry-instrument` wrapper. Before proceeding, configure the auto instrumentation tool. The wrapper is configured using either CLI arguments or environment variables.

The following example uses environment variables.

```bash
# set exporter headers. These are the headers used when calling the Coralogix Endpoint
export OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer%20[your-private-key], CX-Application-Name=dev-traces, CX-Subsystem-Name=dev-traces'

# set the coralogix endpoint for traces
export OTEL_EXPORTER_OTLP_ENDPOINT='ingress.[[DOMAIN_VALUE]]:443'

# set the exporter protocol, in this case we're using grpc
export OTEL_TRACES_EXPORTER=otlp_proto_grpc
```

**Notes**:

- The values of the environment variable OTEL_EXPORTER_OTLP_HEADERS must be URL encoded. The example uses the value %20 to represent a space between the bearer and the token.
- Choose the ingress.\[[DOMAIN_VALUE]\]:443 endpoint that corresponds to your Coralogix [domain](https://coralogix.com/docs/user-guides/account-management/account-settings/coralogix-domain/index.md) using the domain selector at the top of the page.
- View [this page](https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/) for a list of all the environment variable options for configuring the `opentelemetry-instrument` tool.

**STEP 2**. Once you’ve set your environment variables, run the application.

```bash
opentelemetry-instrument \
    --traces_exporter console,otlp \
    --metrics_exporter none \
    --service_name py-auto-instr-test \
    python app.py
```

This will send the traces to the console and to the otel protocol. And will not send metrics.

The application will be served on port `5001`. A trace can be triggered by calling the endpoint `localhost:5001/roll`.

### Metrics

To add metrics, add `otlp` as an argument to the `--metrics_exporter` flag.

```bash
opentelemetry-instrument \
    --traces_exporter console,otlp \
    --metrics_exporter console,otlp \
    --service_name py-auto-instr-test \
    python app.py
```

**Notes**:

- The metrics collected will depend on supporting the underlying framework being instrumented. View [this page](https://opentelemetry.io/ecosystem/registry/?language=python&component=instrumentation) for a list of all supported frameworks.
- Forwarding metrics to Coralogix is required for leveraging Coralogix platform functionalities that depend on service applicative metrics from OpenTelemetry (OTEL). However, it is **not** required for utilizing Coralogix [APM features](https://coralogix.com/docs/user-guides/apm/getting-started/apm-onboarding-tutorial/index.md).

## Programatic instrumentation

### Traces

**STEP 1.**  Create a file `app.py` with the following content:

```py
from os import environ
import random

from flask import Flask

# opentelemetry imports
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.sampling import StaticSampler, Decision
from opentelemetry import trace

# coralogix sampler
from coralogix_opentelemetry.trace.samplers import CoralogixTransactionSampler
```

**STEP 2.** Define the headers necessary for calling the Coralogix endpoint and use them to define an exporter and tracer. An exporter is the mechanism responsible for exporting/sending traces during runtime.

**Notes**:

- CX_ENDPOINT: Choose the ingress.\[[DOMAIN_VALUE]\]:443 endpoint that corresponds to your Coralogix [domain](https://coralogix.com/docs/user-guides/account-management/account-settings/coralogix-domain/index.md) using the domain selector at the top of the page.
- The `headers` variable is a `string` and not a `dictionary`.

```py
headers = ', '.join([
    f'authorization=Bearer%20{environ.get("CX_TOKEN")}',
    'CX-Application-Name=my-instrumented-app',
    'CX-Subsystem-Name=my-instrumented-app'
])

tracer_provider = TracerProvider(
    resource=Resource.create({
        SERVICE_NAME: 'my-instrumented-app'
    }),
    sampler=CoralogixTransactionSampler(StaticSampler(Decision.RECORD_AND_SAMPLE))
)

# set up an OTLP exporter to send spans to coralogix directly.
exporter = OTLPSpanExporter(
    endpoint=environ.get('CX_ENDPOINT'),
    headers=headers
)

# set up a span processor to send spans to the exporter
span_processor = SimpleSpanProcessor(exporter)
# span_processor = ConsoleSpanExporter(exporter)

# add the span processor to the tracer provider
tracer_provider.add_span_processor(span_processor)
trace.set_tracer_provider(tracer_provider)

instrumentor = FlaskInstrumentor()
tracer = trace.get_tracer_provider().get_tracer(__name__)
```

**STEP 3.** Define the Flask application and instrument the functions.

```text
# initialise flask app
app = Flask(__name__)
instrumentor.instrument_app(app) # Instrument the flask app

@app.route("/roll")
def roll():
    res = dice()
    span = trace.get_current_span()
    span.set_attribute("roll.value", res) # Set custom tags
    return f"you rolled {res}\\n"

# example of adding a span to a function using a decorator with the tracer we defined in step 2
@tracer.start_as_current_span("diceFunc")
def dice():
    return random.randint(1, 6)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)
```

The code above has 2 functions: `roll` and `dice`. Select one depending on the specifics of your use case and codebase.

| Function | Description                                             | Instrumentation                                            |
| -------- | ------------------------------------------------------- | ---------------------------------------------------------- |
| roll     | The HTTP handle for the /roll path                      | Instrumented by defining a trace within the function body. |
| dice     | A simple function that returns a number between 1 and 6 | Instrumented using a decorator                             |

**STEP 4.** To run the app, set the required environment variables:

- CX_TOKEN
- CX_ENDPOINT

**Notes**:

- When using a Python virtual environment, these environment variables must be set inside that environment.

Run the dev server:

```bash
python app.py
```

To produce a trace, go to: <http://localhost:5001/roll>.

**STEP 5**. Confirm that your traces are being sent to Coralogix. From your Coralogix toolbar, navigate to **Explore** > **Tracing.** You should now see the traces generated by your application.

### Metrics

Forwarding metrics to Coralogix is required for leveraging Coralogix platform functionalities that depend on service applicative metrics from OpenTelemetry (OTEL). However, it is **not** required for utilizing Coralogix [APM features](https://coralogix.com/docs/user-guides/apm/getting-started/apm-onboarding-tutorial/index.md).

**STEP 1.** Define the metrics exporter and metrics provider.

```py
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    PeriodicExportingMetricReader,
)

from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from os import environ

# define headers. Single string with comma separated key-value pairs
# Authorization, CX-Application-Name and CX-Subsystem-Name are mandatory
# when calling the coralogix endpoint directly.
headers = ', '.join([
    f'Authorization=Bearer%20{environ.get("CX_TOKEN")}',
    "CX-Application-Name=manual-instro-traces",
    "CX-Subsystem-Name=manual-instro-traces",
])

# set up an OTLP exporter to send spans to coralogix directly.
exporter = OTLPMetricExporter(
    endpoint=environ.get('CX_ENDPOINT'),
    headers=headers,
)

metric_reader = PeriodicExportingMetricReader(exporter)
provider = MeterProvider(metric_readers=[metric_reader])

# Sets the global default meter provider
metrics.set_meter_provider(provider)

# Creates a meter from the global meter provider
meter = metrics.get_meter("my.meter.name")
```

Above, the `meter` created a counter metric called `http.request.counter`. The counter counts the number of requests made to the `/roll` endpoint.

View [this page](https://opentelemetry.io/docs/specs/otel/metrics/api/#meter) for more information on the `meter` and a list of other metric types that can be defined.

**STEP 3.** Add the application logic and instrument the `/roll` endpoint.

```py
from random import randint
from flask import Flask

app = Flask(__name__)

@app.route("/roll")
def roll_dice():
    # increment the counter
    http_req_counter.add(1, {"request.path": '/roll'})
    return str(do_roll())

def do_roll():
    res = randint(1, 6)
    return f"you've rolled {res}"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)
```

Note that counter is incremented each time the `/roll` endpoint is called.

**STEP 4.** Execute.

To run the app as before, set the required environment variables:

- CX_TOKEN
- CX_ENDPOINT

```bash
python app.py
```

**STEP 5**. Confirm that your metrics are being sent to Coralogix. From your Coralogix toolbar, navigate to **Grafana** > **Explore** > **Metric Browser**. Search for the name of your metric.

## Manual instrumentation

Implement the same example above using manual instrumentation.

### Traces

**STEP 1.** Create a file `app.py` and import the required packages.

```py
from os import environ
from random import randint

from flask import Flask

# opentelemetry imports
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.sampling import StaticSampler, Decision

# coralogix sampler
from coralogix_opentelemetry.trace.samplers import CoralogixTransactionSampler
```

**STEP 2.** Define the headers necessary for calling the Coralogix endpoint and use them to define an exporter and tracer. An exporter is the mechanism responsible for exporting / sending traces during runtime.

**Notes**:

- CX_ENDPOINT: Choose the ingress.\[[DOMAIN_VALUE]\]:443 endpoint that corresponds to your Coralogix [domain](https://coralogix.com/docs/user-guides/account-management/account-settings/coralogix-domain/index.md) using the domain selector at the top of the page.
- The `headers` variable is a `string` and not a `dictionary`.

```py
# define headers. Single string with comma separated key-value pairs
# Authorization, CX-Application-Name and CX-Subsystem-Name are mandatory when calling the coralogix endpoint directly.
headers = ', '.join([
    f'Authorization=Bearer%20{environ.get("CX_TOKEN")}',
    "CX-Application-Name=<APPLICATION NAME>",
    "CX-Subsystem-Name=<SUBSYSTEM NAME>",
])


# create a tracer provider
tracer_provider = TracerProvider(
    resource=Resource.create({
        SERVICE_NAME: '<SERVICE NAME>'
    }),
    sampler=CoralogixTransactionSampler(StaticSampler(Decision.RECORD_AND_SAMPLE)))

# set up an OTLP exporter to send spans to coralogix directly.
exporter = OTLPSpanExporter(
    endpoint=environ.get('CX_ENDPOINT'),
    headers=headers,
)

# set up a span processor to send spans to the exporter
span_processor = SimpleSpanProcessor(exporter)
# span_processor = ConsoleSpanExporter(exporter)

# add the span processor to the tracer provider
tracer_provider.add_span_processor(span_processor)

# set trace provider
trace.set_tracer_provider(tracer_provider)

# create a tracer
tracer = trace.get_tracer_provider().get_tracer(__name__)
```

```py
# initialise flask app
app = Flask(__name__)

# example of adding a span to a function using a context manager
# this is useful when you want to dynamic attribuf"http://{os.getenv('OTEL_COLLECTOR_SERVICE_HOST')}:4318"tes to the span
@app.route("/roll")
def roll():
    # create a span
    with tracer.start_as_current_span("roll_v2") as span:
        span.set_attribute("http.method", "GET")
        span.set_attribute("http.host", "localhost")
        span.set_attribute("http.url", "/v2/rolldice")
        res = dice()
        span.set_attribute("roll.value", res)
        return f"you rolled {res}\\n"

# example of adding a span to a function using a decorator
@tracer.start_as_current_span("diceFunc")
def dice():
    return randint(1, 6)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)
```

The code above has 2 functions: `roll` and `dice`. Select one depending on the specifics of your use-case and codebase.

| Function | Description                                             | Instrumentation                                            |
| -------- | ------------------------------------------------------- | ---------------------------------------------------------- |
| roll     | The HTTP handle for the /roll path                      | Instrumented by defining a trace within the function body. |
| dice     | A simple function that returns a number between 1 and 6 | Instrumented using a decorator                             |

**STEP 4.** To run the app, set the required environment variables:

- CX_TOKEN
- CX_ENDPOINT

**Notes**:

- When using a Python virtual environment, these environment variables must be set inside that environment.

```bash
python app.py
```

**STEP 5**. Confirm that your traces are being sent to Coralogix. From your Coralogix toolbar, navigate to **Explore** > **Tracing.** You should now see the traces generated by your application.

### Metrics

Like traces, metrics can also be instrumented manually. Choose this option if there are specific metrics that you wish to extract from your application runtime context.

**Notes**:

- The dependencies are the same as those utilized in the `requirements.txt` above for traces.
- Forwarding metrics to Coralogix is required for leveraging Coralogix platform functionalities that depend on service applicative metrics from OpenTelemetry (OTEL). However, it is **not** required for utilizing Coralogix [APM features](https://coralogix.com/docs/user-guides/apm/getting-started/apm-onboarding-tutorial/index.md).

**STEP 1.** Define the metrics exporter and metrics provider.

```py
from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    PeriodicExportingMetricReader,
)

from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from os import environ

# define headers. Single string with comma separated key-value pairs
# Authorization, CX-Application-Name and CX-Subsystem-Name are mandatory
# when calling the coralogix endpoint directly.
headers = ', '.join([
    f'Authorization=Bearer%20{environ.get("CX_TOKEN")}',
    "CX-Application-Name=manual-instro-traces",
    "CX-Subsystem-Name=manual-instro-traces",
])

# set up an OTLP exporter to send spans to coralogix directly.
exporter = OTLPMetricExporter(
    endpoint=environ.get('CX_ENDPOINT'),
    headers=headers,
)

metric_reader = PeriodicExportingMetricReader(exporter)
provider = MeterProvider(metric_readers=[metric_reader])

# Sets the global default meter provider
metrics.set_meter_provider(provider)

# Creates a meter from the global meter provider
meter = metrics.get_meter("my.meter.name")
```

Above, the `meter` created a counter metric called `http.request.counter`. The counter counts the number of requests made to the `/roll` endpoint.

View [this page](https://opentelemetry.io/docs/specs/otel/metrics/api/#meter) for more information on the `meter` and a list of other metric types that can be defined.

**STEP 3.** Add the application logic and instrument the `/roll` endpoint.

```py
from random import randint
from flask import Flask

app = Flask(__name__)

@app.route("/roll")
def roll_dice():
    # increment the counter
    http_req_counter.add(1, {"request.path": '/roll'})
    return str(do_roll())

def do_roll():
    res = randint(1, 6)
    return f"you've rolled {res}"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001)
```

Note that counter is incremented each time the `/roll` endpoint is called.

**STEP 4.** Execute.

To run the app as before, set the required environment variables:

- CX_TOKEN
- CX_ENDPOINT

```bash
python app.py
```

**STEP 5**. Confirm that your metrics are being sent to Coralogix. From your Coralogix toolbar, navigate to **Grafana** > **Explore** > **Metric Browser**. Search for the name of your metric.

## Additional resources

|                             |                                                                                                      |
| --------------------------- | ---------------------------------------------------------------------------------------------------- |
| OpenTelemetry documentation | [OpenTelemetry documentation](https://opentelemetry.io/docs/instrumentation/python/getting-started/) |
| Coralogix Endpoints         | [Coralogix Endpoints](https://coralogix.com/docs/integrations/coralogix-endpoints/index.md)          |

## Support

**Need help?**

Our world-class customer success team is available 24/7 to walk you through your setup and answer any questions that may come up.

Contact us **via our in-app chat** or by emailing [support@coralogix.com](mailto:support@coralogix.com).
