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.

What did you think of this content?