|
| 1 | +# Introduction to OpenTelemetry |
| 2 | + |
| 3 | +[OpenTelemetry (OTEL)](https://opentelemetry.io/) is an open-source |
| 4 | +observability framework for cloud-native software. It provides a standard for |
| 5 | +collecting, processing, and exporting telemetry data (such as traces, metrics, |
| 6 | +and logs) from applications. By using OpenTelemetry, you can gain deep insights |
| 7 | +into the performance and behavior of your distributed systems, making it easier |
| 8 | +to monitor, troubleshoot, and optimize your applications. |
| 9 | + |
| 10 | +OpenTelemetry is vendor-agnostic and supports integration with many popular |
| 11 | +observability backends, including [Grafana](https://grafana.com/), |
| 12 | +[Jaeger](https://www.jaegertracing.io/), [Zipkin](https://zipkin.io/), |
| 13 | +[Prometheus](https://prometheus.io/), and others. It is a unified standard that |
| 14 | +simplifies the process of instrumenting code and collecting telemetry data, |
| 15 | +enabling to build robust monitoring and tracing solutions with small effort. |
| 16 | + |
| 17 | +In the context of web frameworks like BlackSheep, OpenTelemetry can be used to |
| 18 | +automatically trace incoming requests, outgoing HTTP calls, and other |
| 19 | +operations, providing end-to-end visibility into your application's execution |
| 20 | +flow. |
| 21 | + |
| 22 | +## Enabling OpenTelemetry in BlackSheep |
| 23 | + |
| 24 | +BlackSheep offers built-in support for OpenTelemetry since version `2.3.2`, but |
| 25 | +it is anyway simple to use OpenTelemetry in previous versions of the web |
| 26 | +framework. |
| 27 | + |
| 28 | +/// tab | Since v2.3.2 |
| 29 | + |
| 30 | +To enable OpenTelemetry integration starting with version `2.3.2`, you can |
| 31 | +import common functions from the `blacksheep.server.otel` namespace. |
| 32 | + |
| 33 | +```python |
| 34 | +# To use exporters of your choice: |
| 35 | +from blacksheep.server.otel import use_open_telemetry |
| 36 | + |
| 37 | +# To use a service that supports the OpenTelemetry Protocol: |
| 38 | +from blacksheep.server.otel.otlp import use_open_telemetry_otlp |
| 39 | + |
| 40 | +``` |
| 41 | + |
| 42 | +More information is provided below. |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +/// |
| 47 | + |
| 48 | +/// tab | Before v2.3.2 |
| 49 | + |
| 50 | +To enable OpenTelemetry integration before version `2.3.2`, follow the example |
| 51 | +included in [BlackSheep-Examples/otel](https://github.com/Neoteroi/BlackSheep-Examples/tree/main/otel). |
| 52 | + |
| 53 | +- Copy the code in the [`otel/__init__.py`](https://github.com/Neoteroi/BlackSheep-Examples/blob/main/otel/otel/__init__.py) |
| 54 | + and [`otel/otlp.py`](https://github.com/Neoteroi/BlackSheep-Examples/blob/main/otel/otel/otlp.py) folder |
| 55 | + into your own system. |
| 56 | +- Follow the instructions in the repository and the information below. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +/// |
| 61 | + |
| 62 | +The common code consists of a middleware that enables tracing of all web requests |
| 63 | +and exceptions (`OTELMiddleware`), logging instrumentation, and application |
| 64 | +callbacks to ensure logs are collected and flushed properly. |
| 65 | + |
| 66 | +The common code is _vendor-agnostic_ because it is intentionally abstracted |
| 67 | +from specific observability services. Depending on the service you want to use, |
| 68 | +you need to configure specific _exporters_, which are classes responsible to |
| 69 | +send collected logs and traces towards a certain OTEL service. If the service |
| 70 | +you intend to use supports the OpenTelemetry Protocol (OTLP), you can use the |
| 71 | +`use_open_telemetry_otlp` method imported from `otlp.py`. This method uses |
| 72 | +`OTLP` exporters, which rely on standard environment variables. The |
| 73 | +documentation below provides an example on how to use Grafana in such scenario. |
| 74 | + |
| 75 | +## Requirements |
| 76 | + |
| 77 | +```bash |
| 78 | +pip install opentelemetry-distro |
| 79 | + |
| 80 | +opentelemetry-bootstrap --action=install |
| 81 | +``` |
| 82 | + |
| 83 | +To work using the OTLP protocol (for instance, with [Grafana](https://grafana.com/)), |
| 84 | +install the `opentelemetry-exporter-otlp` package: |
| 85 | + |
| 86 | +```bash |
| 87 | +pip install opentelemetry-exporter-otlp |
| 88 | +``` |
| 89 | + |
| 90 | +Install other dependencies depending on the backend service you intend to use. |
| 91 | +For instance, for *Azure Application Insights*, install `azure-monitor-opentelemetry-exporter`: |
| 92 | + |
| 93 | +```bash |
| 94 | +pip install azure-monitor-opentelemetry-exporter |
| 95 | +``` |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## Example: Grafana |
| 100 | + |
| 101 | +There are several ways to integrate with Grafana. This documentation describes |
| 102 | +only manual setup using environment variables for the OTLP protocol. This |
| 103 | +approach is flexible because it only requires outgoing HTTP connections, and |
| 104 | +works well in scenarios where installing agents like `Grafana Alloy` is not |
| 105 | +easy or not possible (e.g. cloud `PaaS` services). |
| 106 | + |
| 107 | +- Install the required dependencies like described above. |
| 108 | +- Obtain environment variables for the OTLP protocol, using the _OpenTelemetry |
| 109 | + getting started guide_ and the option _Send OpenTelemetry data directly to the Grafana Cloud OTLP endpoint._ |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | +Obtain the environment variables with the following names: |
| 114 | + |
| 115 | +``` |
| 116 | +OTEL_RESOURCE_ATTRIBUTES="..." |
| 117 | +OTEL_EXPORTER_OTLP_ENDPOINT="..." |
| 118 | +OTEL_EXPORTER_OTLP_HEADERS="..." |
| 119 | +OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" |
| 120 | +``` |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | +Configure env variables for your application. |
| 125 | + |
| 126 | +/// admonition | Python dot-env. |
| 127 | + type: tip |
| 128 | + |
| 129 | +One way to handle env variables for local development is using the |
| 130 | +`python-dotenv` library and storing the env variables in a `.env` file, then |
| 131 | +load variables using the `load_dotenv` function imported from `dotenv`. |
| 132 | +In such case, ensure that `.env` is included in `.gitignore`. |
| 133 | + |
| 134 | +```python |
| 135 | +from dotenv import load_dotenv |
| 136 | + |
| 137 | +load_dotenv() |
| 138 | +``` |
| 139 | + |
| 140 | +/// |
| 141 | + |
| 142 | +Enable data collection: |
| 143 | + |
| 144 | +```python |
| 145 | +from blacksheep import Application |
| 146 | +from blacksheep.server.otel.otlp import use_open_telemetry_otlp |
| 147 | + |
| 148 | +app = Application() |
| 149 | + |
| 150 | +use_open_telemetry_otlp(app) |
| 151 | +``` |
| 152 | + |
| 153 | +A trace is produced for each web request and for all handled and unhandled |
| 154 | +exceptions. For unhandled exceptions, OpenTelemetry includes the full |
| 155 | +stacktrace of the exception. |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | +## Example: Azure Application Insights |
| 162 | + |
| 163 | +This documentation describes how to integrate with an Azure Application Insights |
| 164 | +service. |
| 165 | + |
| 166 | +- Install the required dependencies like described above. |
| 167 | +- Obtain the connection string of an [Azure Application Insights service](https://learn.microsoft.com/en-us/azure/azure-monitor/app/connection-strings). |
| 168 | +- Configure tracing like in the following example: |
| 169 | + |
| 170 | +```python |
| 171 | +from azure.monitor.opentelemetry.exporter import ( |
| 172 | + AzureMonitorLogExporter, |
| 173 | + AzureMonitorTraceExporter, |
| 174 | +) |
| 175 | +from blacksheep import Application |
| 176 | +from blacksheep.server.otel import use_open_telemetry |
| 177 | + |
| 178 | + |
| 179 | +def use_application_insights( |
| 180 | + app: Application, |
| 181 | + connection_string: str, |
| 182 | +): |
| 183 | + """ |
| 184 | + Configures OpenTelemetry for a BlackSheep application using Azure Application Insights. |
| 185 | +
|
| 186 | + Sets up logging and tracing exporters for Azure Monitor using the provided connection string. |
| 187 | +
|
| 188 | + Args: |
| 189 | + app: The BlackSheep Application instance. |
| 190 | + connection_string: Azure Application Insights connection string. |
| 191 | + """ |
| 192 | + use_open_telemetry( |
| 193 | + app, |
| 194 | + AzureMonitorLogExporter(connection_string=connection_string), |
| 195 | + AzureMonitorTraceExporter(connection_string=connection_string), |
| 196 | + ) |
| 197 | + |
| 198 | +app = Application() |
| 199 | +use_application_insights(app, "YOUR_CONN_STRING") |
| 200 | +``` |
| 201 | + |
| 202 | +Observe how web requests and errors are displayed: |
| 203 | + |
| 204 | + |
| 205 | + |
| 206 | +## Logging dependencies |
| 207 | + |
| 208 | +When using OpenTelemetry you can handle your own tracing explicitly. |
| 209 | +The BlackSheep code includes an asynchronous context manager and example code |
| 210 | +for granular control of tracing. |
| 211 | + |
| 212 | +```python |
| 213 | +from blacksheep.server.otel import logcall |
| 214 | + |
| 215 | + |
| 216 | +@logcall("Example") |
| 217 | +async def dependency_example(): |
| 218 | + await asyncio.sleep(0.1) |
| 219 | + |
| 220 | + |
| 221 | +@app.router.get("/") |
| 222 | +async def home(request) -> Response: |
| 223 | + await dependency_example() |
| 224 | + return text("Hello, traced BlackSheep!") |
| 225 | +``` |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +The following screenshots illustrate how dependencies are displayed in Grafana |
| 230 | +and Azure Application Insights: |
| 231 | + |
| 232 | +[](./img/grafana-dependency.png) |
| 233 | + |
| 234 | +[](./img/azure-app-insights-dependency.png) |
| 235 | + |
| 236 | +/// admonition | Dependencies with Azure Application Insights. |
| 237 | + type: example |
| 238 | + |
| 239 | +For Azure, use a decorator that sets the `az.namespace` information like in |
| 240 | +the example provided in [_BlackSheep-Examples_](https://github.com/Neoteroi/BlackSheep-Examples/blob/main/otel/otel/azure.py). |
| 241 | + |
| 242 | +/// |
| 243 | + |
| 244 | +## Working with spans |
| 245 | + |
| 246 | +The provided `OTELMiddleware` ensures that a tracing context, called "span", is |
| 247 | +created for each request and response cycle. To include additional information |
| 248 | +to a web request cycle, obtain the current span like in the following example: |
| 249 | + |
| 250 | +```python |
| 251 | +from opentelemetry import trace |
| 252 | + |
| 253 | + |
| 254 | +@app.router.get("/") |
| 255 | +async def home(request) -> Response: |
| 256 | + span = trace.get_current_span() |
| 257 | + span.set_attribute("custom.info", "This is extra info for the request") |
| 258 | + return text("Hello, traced BlackSheep!") |
| 259 | +``` |
| 260 | + |
| 261 | +`trace.get_current_span()` works by using context variables (such as Python's |
| 262 | +[`contextvars.ContextVar`](https://docs.python.org/3/library/contextvars.html)) |
| 263 | +under the hood. Context variables allow each asynchronous task or coroutine to |
| 264 | +have its own independent context, so the current span is correctly tracked even |
| 265 | +when code is running concurrently. This ensures that in async code, each task |
| 266 | +sees its own current span, avoiding conflicts or leaks between concurrent |
| 267 | +executions. |
0 commit comments