Rust

This guide shows you how to instrument a Rust application with the upstream OpenTelemetry Rust SDK 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:
    1cargo --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.

Setup#

1 Set environment variables#

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

1export OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443"
2export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
3export OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>"
4export OTEL_SERVICE_NAME="<service-name>"
5export 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).

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:

1opentelemetry = "0.31"
2opentelemetry_sdk = "0.31"
3opentelemetry-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.

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:

1use opentelemetry::global;
2use opentelemetry::trace::Tracer;
3use opentelemetry_otlp::SpanExporter;
4use opentelemetry_sdk::trace::SdkTracerProvider;
5use opentelemetry_sdk::Resource;
6
7fn init_tracer() -> SdkTracerProvider {
8    let exporter = SpanExporter::builder()
9        .with_http()
10        .build()
11        .expect("Failed to create span exporter");
12
13    SdkTracerProvider::builder()
14        .with_resource(Resource::builder().build())
15        .with_batch_exporter(exporter)
16        .build()
17}
18
19fn main() {
20    let tracer_provider = init_tracer();
21    global::set_tracer_provider(tracer_provider.clone());
22
23    let tracer = global::tracer("my-app");
24    tracer.in_span("main-operation", |_cx| {
25        // your application logic
26    });
27
28    // Flush pending spans before the application exits
29    tracer_provider.shutdown().expect("Failed to shut down tracer provider");
30}

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#

1cargo run

Add attributes to spans#

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

1use opentelemetry::global;
2use opentelemetry::trace::{TraceContextExt, Tracer};
3use opentelemetry::KeyValue;
4
5fn handle_order(order_id: &str) {
6    let tracer = global::tracer("my-app");
7    tracer.in_span("handle-order", |cx| {
8        let span = cx.span();
9        span.set_attribute(KeyValue::new("order.id", order_id.to_string()));
10        // ...
11    });
12}

Other deployment targets#

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

1docker run \
2  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://<MW_UID>.middleware.io:443" \
3  -e OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
4  -e OTEL_EXPORTER_OTLP_HEADERS="authorization=<MW_API_KEY>" \
5  -e OTEL_SERVICE_NAME="<service-name>" \
6  <your-image>
1env:
2  - name: OTEL_EXPORTER_OTLP_ENDPOINT
3    value: "https://<MW_UID>.middleware.io:443"
4  - name: OTEL_EXPORTER_OTLP_PROTOCOL
5    value: "http/protobuf"
6  - name: OTEL_EXPORTER_OTLP_HEADERS
7    value: "authorization=<MW_API_KEY>"
8  - name: OTEL_SERVICE_NAME
9    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 or the Traces explorer in Middleware and filter by your service name.

Environment variables#

VariablePurposeExample
OTEL_EXPORTER_OTLP_ENDPOINTMiddleware OTLP endpoint for your project.https://<uid>.middleware.io:443
OTEL_EXPORTER_OTLP_PROTOCOLOTLP transport used by the exporter.http/protobuf
OTEL_EXPORTER_OTLP_HEADERSAuthenticates requests with your Middleware API key.authorization=<MW_API_KEY>
OTEL_SERVICE_NAMEService name shown in Middleware APM.payments-api
OTEL_RESOURCE_ATTRIBUTESExtra 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 or join our Slack channel.