Auto-Instrumentation is Magic: Using OpenTelemetry Python with Lightstep
Tags: tutorial, opensource, opentelemetry, observability
Published: 2022-08-22 15:04:00

In my [last OpenTelemetry blog post](https://dev.to/lightstep/observability-mythbusters-opentelemetry-to-lightstep-3-ways-in-go-is-possible-44p0), I talked about how to send [OpenTelemetry (OTel)](opentelemetry.io) data to [Lightstep](app.lightstep.com) using Golang. That’s all well and good if you’re a Golang developer, but what if you use Python? Well, my friend, you’re in luck, because today, I’ll be looking at how to send OpenTelemetry data to Lightstep using Python.

As with the OTel Golang post, we can send OTel data to Lightstep (or any other Observability tool that supports [OpenTelemetry Protocol (OTLP)](https://dev.to/lightstep/observability-mythbusters-opentelemetry-to-lightstep-3-ways-in-go-is-possible-44p0), for that matter) in one of 3 ways:

1. Direct from application
2. [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
3. Launchers

In this post, I will dig into each of these three approaches in detail, with code snippets which explain how to get data into [Lightstep Observability](http://app.lightstep.com). Let’s do this!

## OpenTelemetry & Lightstep

Lightstep Observability supports the native [OpenTelemetry Protocol (OTLP)](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md). It can receive data in the OTLP format either via [HTTP](https://opentelemetry.io/docs/concepts/glossary/#http) or [gRPC](https://opentelemetry.io/docs/concepts/glossary/#grpc). You will need to specify which method you wish to use in your code, as we’ll see in the upcoming code snippets.

If you're curious about using gRPC vs HTTP for OpenTelemetry, check out [these docs](https://docs.lightstep.com/docs/send-otlp-over-http-to-lightstep).

> **_Note:_** Other Observability tools that support OTLP include [Honeycomb](https://www.honeycomb.io/blog/all-in-on-opentelemetry/), [Grafana](https://grafana.com/blog/2021/04/13/how-to-send-traces-to-grafana-clouds-tempo-service-with-opentelemetry-collector/), and [Jaeger](https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c).

## Automatic Instrumentation & Python

One thing that’s super cool about using OTel to instrument your Python code is that Python offers automatic (auto) instrumentation. What does this mean? At a high level, it means that you can run a Python OpenTelemetry binary (called [`opentelemetry-instrument`](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/opentelemetry-instrumentation)) that wraps around your Python application, to automagically instrument it. 🪄

More specifically, auto-instrumentation uses shims or bytecode instrumentation agents to intercept your code at runtime or at compile-time to add tracing and metrics instrumentation to the libraries and frameworks you depend on. The beauty of auto-instrumentation is that it requires a minimum amount of effort. Sit back, relax, and enjoy the show. A number of popular Python libraries are auto-instrumented, including [Flask](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-flask) and [Django](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-django). You can find the full list [here](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation).

Manual instrumentation requires adding spans, context propagation, attributes, etc. to your code. It’s akin to commenting your code or writing tests.

Does this mean that you shouldn’t manually instrument? Not at all! Start with auto-instrumentation if it’s available. If the auto-instrumentation isn’t sufficient for your use case (most often it’s not), then add in the manual instrumentation. For example, auto-instrumentation doesn’t know your business logic—it only knows about frameworks and languages—in which case you’ll want to manually instrument your business logic, so that you get that visibility.

## Pre-Requisites

Before we start our tutorial, here are some things that you’ll need:

* A basic understanding of [Python](https://www.python.org/) and [Python virtual environments](https://realpython.com/python-virtual-environments-a-primer/)
* A basic understanding of the [OpenTelemetry Collector](https://lightstep.com/blog/observability-mythbusters-how-hard-is-it-to-get-started-with-opentelemetry#otel-collector-101)
* A basic understanding of how to use [Lightstep Observability](app.lightstep.com)

If you’d like to run the full code examples, you’ll also need:

* A [Lightstep Observability account](https://app.lightstep.com/signup/developer?signup_source=docs)
* A [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens) to tell Lightstep what project to send your traces to
* A working installation of [Python](https://www.python.org/downloads/)
* Docker (to run the OTel Collector locally)

## Direct from Application

If you’re getting started with instrumenting your application with OpenTelemetry, this is probably the most common route taken by most beginners. As the name suggests, we are sending data to a given Observability back-end directly from our application code.

![otel-to-ls-direct](//images.ctfassets.net/ne67e1btg495/4hTlOwHH8j36uF8kHUc0di/f0c47cdc218179f4a52b05a54e6d4a44/otel-to-ls-direct.png "Diagram illustrating sending OpenTelemetry data to Lightstep direct from application")

Our sample application is a Flask application. We will be leveraging both automatic and manual instrumentation.

Let’s look at this in greater detail below.

#### 1- Set up your environment

Let’s set up our working directory and our Python virtual environment 

```bash
mkdir otel_python
cd otel_python

python3 -m venv .
source ./bin/activate
touch server.py
```

Open `server.py`, and paste the following:

```python
from flask import Flask, request
from opentelemetry import trace
from random import randint
tracer = trace.get_tracer_provider().get_tracer(__name__)

app = Flask(__name__)

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

@tracer.start_as_current_span("do_roll")
def do_roll():
    res = randint(1, 6)
    current_span = trace.get_current_span()
    current_span.set_attribute("roll.value", res)
    current_span.set_attribute("operation.name", "Saying hello!")
    current_span.set_attribute("operation.other-stuff", [1, 2, 3])
    return res

if __name__ == "__main__":
    app.run(port=8082, debug=True, use_reloader=False)
```

#### 2- Install the required OTel libraries

These are the libraries that are required to send data to an **_Observability back-end_** (e.g Lightstep).

```bash
# OTel-specific
pip install opentelemetry-distro
pip install opentelemetry-exporter-otlp

# App-specific
pip install flask
pip install requests
```

A few noteworthy items:

* Installing `opentelemetry-distro` will install a number of other dependent packages for instrumenting code, including `opentelemetry-api` and `opentelemetry-sdk`, and our auto-instrumentation wrapper binary, `opentelemetry-instrument`.
* The `opentelemetry-exporter-otlp` package is used to send OTel data to your Observability back-end (e.g. Lightstep). Installing it in turn installs `opentelemetry-exporter-otlp-proto-grpc` (send data via gRPC) and `opentelemetry-exporter-otlp-proto-http` (send data via HTTP).

#### 3- Install auto-instrumentation

As you may recall from earlier in this post, Python auto-instrumentation includes a binary that wraps our Python application and automagically adds some high-level instrumentation for us. But that's only part of the picture. There are [Python auto-instrumentation libraries](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation) available for a number of popular Python libraries (e.g. Flask, requests). Using these auto-instrumentation libraries, along with `opentelemetry-instrument`, gives us auto-instrumentation superpowers. 💪

So how do we install these auto-instrumentation libraries? Well, there's a handy little tool for that, called `opentelemetry-bootstrap`. It was installed as part of our installation of `opentelemetry-distro`.

Let's run it:

```bash
opentelemetry-bootstrap -a install
```

So what does this do? The above command will read through the packages installed in your active `site-packages` folder, and will install the applicable auto-instrumentation libraries. For example, if you already installed the `flask` and `requests` packages (as we did in Step 2), running `opentelemetry-bootstrap -a install` will install `opentelemetry-instrumentation-flask` and `opentelemetry-instrumentation-requests` for you. If you leave out `-a install`, it will simply list out the recommended auto-instrumentation packages to be installed.

For more information on `opentelemetry-bootstrap`, check out the [official OpenTelemetry docs](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/opentelemetry-instrumentation#opentelemetry-bootstrap).

#### 4- Run the app

Here’s where it gets interesting! Normally to run this app, we’d run it like this:

```bash
python server.py
```

But if we did that, we wouldn’t be sending any OTel data to Lightstep. So we must instead do this:

```bash
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="lightstep-access-token=<LS_ACCESS_TOKEN>"

opentelemetry-instrument \
           --traces_exporter console,otlp_proto_grpc \
           --metrics_exporter console,otlp_proto_grpc \
           --service_name test-py-auto-otlp-grpc-server \
           --exporter_otlp_endpoint "ingest.lightstep.com:443" \
           python server.py
```

Some noteworthy items:

* Replace `<LS_ACCESS_TOKEN>` with your own [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens).
* `traces_exporter` and `metrics_exporter` specify which trace exporter and which metrics to use, respectively. In this case, traces and metrics are being exported to `console` (stdout) and to `otlp_proto_grpc`. The `otlp_proto_grpc` option tells `opentelemetry-instrument` to send it to an endpoint that accepts OTLP via gRPC. The full list of available options for `traces_exporter` can be found [here](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/opentelemetry-instrumentation).
* `service_name` sets the name of the service. This is the value that will show up in the [Lightstep service explorer](/docs/view-traces). Be sure to replace `<service_name>` with your own service name.
* `exporter_otlp_endpoint` tells `opentelemetry-instrument` to send the traces to gRPC endpoint `ingest.lightstep.com:443` (i.e. Lightstep).

Sample output:

![Screen captuere of Python server app sample output](//images.ctfassets.net/ne67e1btg495/33H6uIrUld7fJmIw2mc7f2/df3e74ca607b215c9ba9a7565c9bf147/python-server-output-nogrpc-debug.png "Screen captuere of Python server app sample output")

Want to use HTTP instead of gRPC? First, you need to make sure that the `pip` package `opentelemetry-exporter-otlp-proto-http` is installed (should be automagically installed as part of installing `opentelemetry-exporter-otlp`).

Next, your `opentelemetry-instrument` command would look like this:

```bash
opentelemetry-instrument \
  --traces_exporter console,otlp_proto_http \
  --metrics_exporter console \
  --service_name test-py-auto-otlp-server \
  --exporter_otlp_traces_endpoint "https://ingest.lightstep.com:443/traces/otlp/v0.9" \
  python server.py
```

Some noteworthy items:

* The `traces_exporter` uses `otlp_proto_http` instead of `otlp_proto_grpc`.
* The `exporter_otlp_traces_endpoint` endpoint is `https://ingest.lightstep.com/traces/otlp/v0.9` (see [docs](http://localhost:4000/docs/test-connectivity-to-microsatellites), instead of `ingest.lightstep.com:443`).
* There is currently no metrics support for `otlp_proto_http` and there is no `exporter_otlp_metrics_endpoint` option, which is why metrics are being sent to `console` only.


#### 5- Call the /rolldice service

Open up a new terminal window, and run the following:

```bash
curl http://localhost:8082/rolldice
```

Running the above line will return a random number between 1 and 6. Nothing too remarkable there. But if you look over at the terminal window for `server.py`, you’ll notice something in the output:

![Screen capture of Python server output after client call](//images.ctfassets.net/ne67e1btg495/zCEwwQy5yguJyElrCs4tk/99289e8098e1fb6d41533b8f33f07875/python-server-client-output.png "Screen capture of Python server output after client call")

We see the trace from `server.py`! Why are we seeing this here? Because we set the `--traces_exporter` flag to `console,otlp_proto_grpc`, which exports to Lightstep via OTLP _and_ to the console.

#### 6- See it in Lightstep

![python-server-otlp-ls Screen capture of trace in Lightstep - OTLP direct](//images.ctfassets.net/ne67e1btg495/29xrp2z3QT8LWQV5daHKDD/bbd535f6b8918e9e26f6485c3a5bf61d/python-server-otlp-ls.png "Screen capture of trace in Lightstep - OTLP direct")

## OpenTelemetry Collector

The next approach to sending data to an Observability back-end is by way of the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). For non-development setups, this is the recommended approach to send OpenTelemetry data to your Observability back-end.

![otel-to-ls-via-collector](//images.ctfassets.net/ne67e1btg495/3yCGLRDT0Efbq1F1sQO4Q5/a20e644bc17ec8b03047829a5b1c8699/otel-to-ls-via-collector.png "Diagram showing sending OTel data to Lightstep via the Collector")

Sending OTel data via the OTel Collector is _almost identical_ to what we did in the Direct from Application example above. The only difference is that:

* We need to run an OTel Collector
* When we run `opentelemetry-instrument`, our options are slightly different

Let’s look at this in greater detail below.

#### 1- Follow Steps 1-3 from the “_Direct from Application_” example

#### 2- Run the Collector

First, we need to configure our Collector for sending data to Lightstep. We do this by grabbing `collector.yaml` from Lightstep’s `opentelemetry-examples` repo.

```bash
git clone git@github.com:lightstep/opentelemetry-examples.git
```

Open up a new terminal window. First, you'll need to edit the `collector.yaml` file. Be sure to replace `${LIGHTSTEP_ACCESS_TOKEN}` with your own [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens).

Now you can start up the Collector:

```bash
cd opentelemetry-examples/collector/vanilla
docker run -it --rm -p 4317:4317 -p 4318:4318 \
    -v $(pwd)/collector.yaml:/otel-config.yaml \
    --name otelcol otel/opentelemetry-collector-contrib:0.53.0  \
    "/otelcol-contrib" \
    "--config=otel-config.yaml"
```

Sample output:

![Screen capture of OTel Collector startup sequence](//images.ctfassets.net/ne67e1btg495/4jerLuY4TXd8DKooOmtGUN/cc73ce20b97ea3c83106f799b4396347/collector-startup.png "Screen capture of OTel Collector startup sequence")

#### 3- Run the app

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

Notice that the endpoint isn't specified. That's because it assumes that you are using the default Collector gRPC endpoint, `0.0.0.0:4317`. The above command is the equivalent of saying:

```bash
opentelemetry-instrument \
  --traces_exporter console,otlp \
  --metrics_exporter console,otlp\
  --service_name test-py-auto-collector-server \
  --exporter_otlp_endpoint "0.0.0.0:4317" \
  --exporter_otlp_insecure true \
  python server.py
```

If you specify the endpoint, you must also specify `--exporter_otlp_insecure true` if a certificate isn't configured with your Collector.

Some additional noteworthy items:

* `otlp`, used in configuring `traces_exporter` and `metrics_exporter`, is equivalent to using `otlp_proto_grpc`
* To use a different Collector endpoint, simply replace it with your own. If you don't have a Certificate configured with your Collector, remember to add `--exporter_otlp_insecure true` 
* You don't need to set `OTEL_EXPORTER_OTLP_TRACES_HEADERS`, because that's already configured in the Collector's [config.yml](https://github.com/lightstep/opentelemetry-examples/blob/main/collector/vanilla/collector.yaml) file.

If you wish to use HTTP instead of gRPC, the command would then look like this:

```bash
opentelemetry-instrument \
  --traces_exporter console,otlp_proto_http \
  --metrics_exporter console,otlp_proto_http \
  --service_name test-py-auto-collector-server \
  python server.py
```

Which is the same as saying:

```bash
opentelemetry-instrument \
  --traces_exporter console,otlp_proto_http \
  --metrics_exporter console,otlp_proto_http \
  --service_name test-py-auto-collector-server \
  --exporter_otlp_endpoint "http://0.0.0.0:4318" \
  --exporter_otlp_insecure true \
  python server.py
```

Again, if you wish to use your own Collector endpoint, simply replace the value in `exporter_otlp_endpoint`, making sure that you prefix it with `http://` or `https://`. Remember to add `--exporter_otlp_insecure true` if you don't have a Certificate configured with your Collector.

Okay. Enough banter. Let's look at the sample output:

![Screen captuere of Python server app sample output](//images.ctfassets.net/ne67e1btg495/33H6uIrUld7fJmIw2mc7f2/df3e74ca607b215c9ba9a7565c9bf147/python-server-output-nogrpc-debug.png "Screen captuere of Python server app sample output")

#### 4- Call the /rolldice service

Open up a new terminal window, and run the following:

```bash
curl http://localhost:8082/rolldice
```

Sample output:

![Screen capture of Python server output after client call](//images.ctfassets.net/ne67e1btg495/zCEwwQy5yguJyElrCs4tk/99289e8098e1fb6d41533b8f33f07875/python-server-client-output.png "Screen capture of Python server output after client call")

Again, we see the trace for `server.py` because we set the --traces_exporter flag to console,otlp, which exports to the Collector via OTLP _and_ to the console.

#### 5- See it in Lightstep

![Screen capture of trace in Lightstep - Collector](//images.ctfassets.net/ne67e1btg495/6Cm2D5uYU21Id4XSd9mcr6/45d9cd4754204d160bb54755ff61f802/python-server-collector-ls.png "Screen capture of trace in Lightstep - Collector")

## Launcher

If you thought it was easy-peasey to send OTel data to Lightstep à la auto-instrumentation binary, then it’s even easier to do it via the [OTel Python Launcher](https://github.com/lightstep/otel-launcher-python)! Think of it as an OTel wrapper to make it extra-easy to send data to Lightstep, by having a bunch of things pre-configured for you to lower that barrier to entry.

Sending OTel data via the Launcher is _almost identical_ to what we did in the Direct from Application example above, with a few minor differences:

* We have fewer packages to (yay!)
* When we run `opentelemetry-instrument`, our options are slightly different

Let’s see it in action shall we?

#### 1- Follow Steps 1-3 from the “_Direct from Application_” example

Minor change: replace the libraries from Step 2 with these:

```
# OTel-specific
pip install opentelemetry-launcher
pip install protobuf==3.20.1

# App-specific
pip install requests
pip install flask
```

We need to force a specific version of `protobuf` because of Launcher compatibility issues with newer versions. This was [already fixed](https://github.com/open-telemetry/opentelemetry-python/pull/2720) in [opentelemetry-python](https://github.com/open-telemetry/opentelemetry-python).

When we install the `opentelemetry-launcher` package, it also does double-duty and doesn’t require that we run `opentelemetry-bootstrap -a install`. 

#### 2- Run the app

Be sure to replace `<LS_ACCESS_TOKEN>` with your own [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens).

```bash
export LS_ACCESS_TOKEN="<LS_ACCESS_TOKEN>"

opentelemetry-instrument \
    --service_name test-py-auto-launcher-server \
    python server.py
```

Looks like we have fewer options, don’t we? Let's dig in a bit to some noteworthy items:

* We don’t need to specify an `--exporter_otlp_traces_endpoint`, because that’s already implicitly done for us, and as set to `ingest.lightstep.com:443`. 
* Instead of setting a messy-looking environment var for our [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens) (`export OTEL_EXPORTER_OTLP_TRACES_HEADERS="lightstep-access-token=<LS_ACCESS_TOKEN>"`), we just have to do this: `export LS_ACCESS_TOKEN="<LS_ACCESS_TOKEN>"`, which looks way cleaner.

If you wish to send your OTel data via a Collector instance first, rather than direct from your application, you would do this instead:

```bash
opentelemetry-instrument \
    --service_name test-py-auto-launcher-server \
    --exporter_otlp_traces_endpoint "0.0.0.0:4317" \
    --exporter_otlp_traces_insecure true \
    python server.py
```

Noteworthy items:

* Do not set `LS_ACCESS_TOKEN`, since that's already configured in the Collector's [config.yml](https://github.com/lightstep/opentelemetry-examples/blob/main/collector/vanilla/collector.yml) file.
* If you attempt to override `exporter_otlp_endpoint` to send traces to a Collector, the traces will be sent directly to `ingest.lightstep.com:443` instead of via the Collector. Instead, you need to override `exporter_otlp_traces_endpoint`, 
* `exporter_otlp_traces_endpoint` sends traces to a Collector running on `0.0.0.0:4317` (gRPC). If you wish to use a different Collector address, simply include `exporter_otlp_traces_endpoint`, using your own Collector's endpoint.
* `exporter_otlp_traces_insecure` is set to `true`. This is required if you are using a Collector *and* if a certificate isn't configured in the Collector.
* There is currently no HTTP support for Python Launchers.

Sample output:

![Screen captuere of Python server app sample output](//images.ctfassets.net/ne67e1btg495/33H6uIrUld7fJmIw2mc7f2/df3e74ca607b215c9ba9a7565c9bf147/python-server-output-nogrpc-debug.png "Screen captuere of Python server app sample output")

#### 3- Call the /rolldice service

Open up a new terminal window, and run the following:

```bash
curl http://localhost:8082/rolldice
```

Sample output:

![Screen captuere of Python server app sample output for Launcher](//images.ctfassets.net/ne67e1btg495/4hMKXhMO5AFySvWw4UyCXo/86f5128f0e4c17c30ff0efb2e484cfda/python-server-launcher-output.png "Screen captuere of Python server app sample output for Launcher")

Notice that since our `opentelemetry-instrument` call didn't specify a `--traces_exporter`, it's the equivalent of saying `--traces_exporter otlp_proto_grpc`. I also means that there's no trace output to the console (stdout).

#### 4- See it in Lightstep

![Screen capture of trace in Lightstep - Launcher](//images.ctfassets.net/ne67e1btg495/5CC4dgB5r5VXGPURY9KDXz/8476025486f151f0eee8faa9f0779ef1/otel-to-ls-via-launcher.png "Screen capture of trace in Lightstep - Launcher")

## Should I always use the auto-instrumentation binary?

Is `opentelemetry-instrument` still helpful even if you’re not using a Python library that’s not auto-instrumented? Personally, I think so! Consider this file, `client.py`:

```python
from sys import argv

from requests import get

from opentelemetry import trace
from opentelemetry.propagate import inject
tracer = trace.get_tracer_provider().get_tracer(__name__)

assert len(argv) == 2

with tracer.start_as_current_span("client"):

   with tracer.start_as_current_span("client-server"):
       headers = {}
       inject(headers)
       requested = get(
           "http://localhost:8082/rolldice",
           params={"param": argv[1]},
           headers=headers,
       )

       assert requested.status_code == 200
```

Let’s run the above program with the auto-instrumentation binary. Be sure to replace `<LS_ACCESS_TOKEN>` with your own [Lightstep Access Token](https://docs.lightstep.com/docs/create-and-manage-access-tokens).

```bash
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="lightstep-access-token=<LS_ACCESS_TOKEN>"

opentelemetry-instrument \
    --traces_exporter console,otlp \
    --service_name test-py-auto-client \
    --exporter_otlp_endpoint "ingest.lightstep.com:443" \
    python client.py test
```

Notice that aside from creating spans in `client.py`, there’s no OTel configuration in there. You don’t configure the service name, the exporter, or the endpoint. That’s all taken care of when you run `opentelemetry-instrument`. Plus, if your code happens to use a library that is auto-instrumented, you don’t have to do anything else.

> **_Note:_** If you’re wondering why we’re executing the command `python client.py test`, it’s because `client.py` takes a single parameter, which in this case is called `test`.

## gRPC Debugging

Do you ever wonder if your gRPC calls are going into a black hole? I definitely do! When I was mucking around with gRPC for the Golang OTel libraries, I learned about some gRPC debug flags that would make my life easier for troubleshooting gRPC connectivity issues. Which of course got me wondering if there was a Python equivalent. Turns out there is. Set these environment variables before running your app, and you’re golden:

```bash
export GRPC_VERBOSITY=debug
export GRPC_TRACE=http,call_error,connectivity_state
```

This means that when we start up our `server.py`, we get something like this:

![Screen captuere of Python server app sample output with gRPC debug](//images.ctfassets.net/ne67e1btg495/3rv0KroUvOnKBeIdYeA6Wq/e11cecbb82bf7974c1bcdf04dac6bad3/python-server-output.png "Screen captuere of Python server app sample output with gRPC debug")

And then when we call our endpoint via `curl`, we get this:

![Screen capture of Python server app sample output with gRPC degug showing successful gRPC call](//images.ctfassets.net/ne67e1btg495/6G0b5jOXy4Vx4NJXdGBz45/3a7c7e50334904f4ef96188257ef2739/grpc-debug-python.png "Screen capture of Python server app sample output with gRPC degug showing successful gRPC call")

The part highlighted above tells me that our gRPC call was successful!

## Final Thoughts

Auto-instrumentation in Python is pretty freaking awesome, and it really lowers the barrier to entry for OpenTelemetry. As we saw with the Direct from Application and Collector examples, the code stays pretty much the same. The only difference is that you need to change up some flags so that the auto-instrumentation binary knows where to send your traces to. Nice and easy!

In case you’re wondering, there is a totally pure OTel Python manual instrumentation approach, which I will cover in a future blog post, so stay tuned! For now, bask in the fact that you learned something super cool today about OTel Python auto-instrumentation!

And now, I will reward you with a picture of my rat Phoebe getting some cuddles.

![Phoebe the rat gets cuddles](//images.ctfassets.net/ne67e1btg495/1JlxaPBnjJMw13EaPw0i5N/e0352eb83215221f5addb47b11800043/phoebe.JPG "Phoebe the rat gets cuddles. Photo by Adriana Villela (https://adri-v.medium.com)")

Peace, love, and code. 🦄 🌈 💫

***

Got questions about OTel instrumentation with Python? Talk to me! Feel free to connect through [e-mail](mailto:devrel@lightstep.com), or hit me up on [Twitter](https://twitter.com/adrianamvillela) or [LinkedIn](https://www.linkedin.com/in/adrianavillela/). Hope to hear from y’all!