---
title: "Rust"
description: "Instrument Rust applications with the OpenTelemetry SDK and send traces directly to Middleware over OTLP/HTTP."
section: "APM"
breadcrumb: "APM > Rust"
canonical: "https://docs.middleware.io/get-started/apm/rust"
---
# Rust

This guide shows you how to instrument a Rust application with the upstream [OpenTelemetry Rust SDK](https://github.com/open-telemetry/opentelemetry-rust) and send traces directly to Middleware. No Middleware Host Agent is required: the OTLP exporter in your application sends data straight to your Middleware account.

## Prerequisites

- **Rust 1.75 or newer** with Cargo. Check using:

  ```bash
  cargo --version
  ```
- **Middleware account & project**: your Middleware **UID** (`https://<uid>.middleware.io`) and **API key** (`MW_API_KEY`)
- **Network access** from your application to `https://<uid>.middleware.io:443`

For a deeper overview of Middleware's OTLP endpoints and headers, see the [OpenTelemetry getting started guide](/get-started/agent/otel).

## Setup

#### 1 Set environment variables

The OpenTelemetry SDK reads its exporter and resource configuration from standard environment variables. Set them wherever your application runs:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<uid>.middleware.io:443"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_RESOURCE_ATTRIBUTES="service.version=<service-version>,deployment.environment=<environment>"
```

Replace:

- `<uid>` with your Middleware project UID.
- `<MW_API_KEY>` with your Middleware API key.
- `<service-name>` with a meaningful name for this service (for example, `payments-api`).

> **Note:** Set `OTEL_EXPORTER_OTLP_ENDPOINT` to the root URL only. The OTLP/HTTP exporter appends `/v1/traces` for you.

#### 2 Add dependencies

Add the OpenTelemetry crates to the `[dependencies]` section of your `Cargo.toml`:

```toml
opentelemetry = "0.31"
opentelemetry_sdk = "0.31"
opentelemetry-otlp = { version = "0.31", features = ["reqwest-rustls"] }
```

The default features of `opentelemetry-otlp` provide the OTLP/HTTP (protobuf) exporter and a blocking HTTP client that runs on the batch exporter's background thread. The `reqwest-rustls` feature adds TLS so the exporter can reach the HTTPS endpoint.

> **Warning:** Do not enable the `reqwest-client` feature alongside the default features. When both the async and the blocking `reqwest` clients are enabled, the exporter cannot pick a client and `SpanExporter::build()` fails with `NoHttpClient`.

#### 3 Initialize the tracer

Create a tracer provider when your application starts, and shut it down before the process exits so buffered spans are flushed:

```rust
use opentelemetry::global;
use opentelemetry::trace::Tracer;
use opentelemetry_otlp::SpanExporter;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;

fn init_tracer() -> SdkTracerProvider {
    let exporter = SpanExporter::builder()
        .with_http()
        .build()
        .expect("Failed to create span exporter");

    SdkTracerProvider::builder()
        .with_resource(Resource::builder().build())
        .with_batch_exporter(exporter)
        .build()
}

fn main() {
    let tracer_provider = init_tracer();
    global::set_tracer_provider(tracer_provider.clone());

    let tracer = global::tracer("my-app");
    tracer.in_span("main-operation", |_cx| {
        // your application logic
    });

    // Flush pending spans before the application exits
    tracer_provider.shutdown().expect("Failed to shut down tracer provider");
}
```

`Resource::builder()` picks up `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`, and the exporter picks up the endpoint and `authorization` header from step 1. The same code works inside an async runtime such as Tokio.

#### 4 Run your application

```bash
cargo run
```

## Add attributes to spans

Use `TraceContextExt` to reach the active span and attach attributes that you can filter on in Middleware:

```rust
use opentelemetry::global;
use opentelemetry::trace::{TraceContextExt, Tracer};
use opentelemetry::KeyValue;

fn handle_order(order_id: &str) {
    let tracer = global::tracer("my-app");
    tracer.in_span("handle-order", |cx| {
        let span = cx.span();
        span.set_attribute(KeyValue::new("order.id", order_id.to_string()));
        // ...
    });
}
```

## Other deployment targets

The OpenTelemetry configuration is the same everywhere; only the way you declare environment variables changes.

#### Docker

```bash
docker run \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<uid>.middleware.io:443" \
  -e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
  -e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
  -e OTEL_SERVICE_NAME="<service-name>" \
  <your-image>
```

#### Kubernetes

```yaml
env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "https://<uid>.middleware.io:443"
  - name: OTEL_EXPORTER_OTLP_PROTOCOL
    value: "http/protobuf"
  - name: OTEL_EXPORTER_OTLP_HEADERS
    value: "authorization=<MW_API_KEY>"
  - name: OTEL_SERVICE_NAME
    value: "<service-name>"
```

Store the API key in a Kubernetes Secret and reference it with `valueFrom.secretKeyRef` in production.

## View your data

After you start the application and send some traffic, wait 3–5 minutes, then open [**APM → Services**](https://app.middleware.io/apm/services) or the **Traces** explorer in Middleware and filter by your service name.

## Environment variables

| Variable | Purpose | Example |
| --- | --- | --- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Middleware OTLP endpoint for your project. | `https://<uid>.middleware.io:443` |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP transport used by the exporter. | `http/protobuf` |
| `OTEL_EXPORTER_OTLP_HEADERS` | Authenticates requests with your Middleware API key. | `authorization=<MW_API_KEY>` |
| `OTEL_SERVICE_NAME` | Service name shown in Middleware APM. | `payments-api` |
| `OTEL_RESOURCE_ATTRIBUTES` | Extra resource attributes attached to every span. | `service.version=1.4.2,deployment.environment=prod` |

## Troubleshooting

- **`NoHttpClient` panic on startup:** both `reqwest-client` and the default `reqwest-blocking-client` features are enabled. Use the dependencies from step 2.
- **No data in Middleware:** confirm the environment variables are set in the process that runs your app (`echo $OTEL_EXPORTER_OTLP_ENDPOINT`), and that the endpoint does not already include `/v1/traces`.
- **Spans missing for short-lived programs:** call `tracer_provider.shutdown()` before exiting. The batch exporter sends spans in the background, and anything still buffered is lost if the process exits first.
- **TLS or connection errors:** make sure `reqwest-rustls` is enabled and that outbound HTTPS to `<uid>.middleware.io:443` is allowed.

Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us page or join our Slack channel.
