This guide shows you how to instrument your Rust application with OpenTelemetry and send traces to SigNoz. Learn how to auto-instrument frameworks like Axum, Actix Web, reqwest, and tonic, or add manual instrumentation for custom spans and attributes.
Prerequisites
- Rust 1.75 or newer
- Cargo (comes with Rust)
- A SigNoz Cloud account or self-hosted SigNoz instance
Tested with OpenTelemetry Rust SDK v0.31.0.
Send traces to SigNoz
Step 1. Set environment variables
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)
Step 2. Add OpenTelemetry dependencies
Add the following to your Cargo.toml under [dependencies]:
opentelemetry = { version = "0.31.0", features = ["trace"] }
opentelemetry_sdk = { version = "0.31.0", features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace", "tls-roots"] }
tokio = { version = "1", features = ["full"] }
tonic = { version = "0.14", features = ["tls-native-roots"] }
The tls-roots feature in opentelemetry-otlp and tls-native-roots in tonic are required for sending data to SigNoz Cloud over HTTPS.
Step 3. Create the tracer initialization
Add the following imports and function to your main.rs:
use opentelemetry_otlp::{SpanExporter, WithTonicConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
fn init_tracer() -> SdkTracerProvider {
let service_name = std::env::var("OTEL_SERVICE_NAME")
.unwrap_or_else(|_| "rust-app".to_string());
// The SDK automatically reads these environment variables:
// - OTEL_EXPORTER_OTLP_ENDPOINT
// - OTEL_EXPORTER_OTLP_HEADERS
// - OTEL_EXPORTER_OTLP_TIMEOUT
let exporter = SpanExporter::builder()
.with_tonic()
.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
.build()
.expect("Failed to create span exporter");
// Resource::builder() includes SDK telemetry attributes automatically
let resource = Resource::builder()
.with_service_name(service_name)
.build();
SdkTracerProvider::builder()
.with_resource(resource)
.with_batch_exporter(exporter)
.build()
}
Then initialize it in main():
let tracer_provider = init_tracer();
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
Call tracer_provider.shutdown() before your process exits to flush buffered spans.
Step 4. Run your application
cargo run
The steps above configure the trace exporter but does not auto-instrument anything out of the box. You can either add framework instrumentation for automatic spans from libraries like Axum, Actix Web, and reqwest, or use manual instrumentation to create custom spans, attributes, and events.
Step 1. Set environment variables in your deployment
Add these environment variables to your Kubernetes deployment manifest:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: 'https://ingest.<region>.signoz.cloud:443'
- name: OTEL_EXPORTER_OTLP_HEADERS
value: 'signoz-ingestion-key=<your-ingestion-key>'
- name: OTEL_SERVICE_NAME
value: '<service-name>'
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)
Step 2. Add OpenTelemetry dependencies
Add the following to your Cargo.toml under [dependencies]:
opentelemetry = { version = "0.31.0", features = ["trace"] }
opentelemetry_sdk = { version = "0.31.0", features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace", "tls-roots"] }
tokio = { version = "1", features = ["full"] }
tonic = { version = "0.14", features = ["tls-native-roots"] }
Step 3. Create the tracer initialization
Add the following imports and function to your main.rs:
use opentelemetry_otlp::{SpanExporter, WithTonicConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
fn init_tracer() -> SdkTracerProvider {
let service_name = std::env::var("OTEL_SERVICE_NAME")
.unwrap_or_else(|_| "rust-app".to_string());
// The SDK automatically reads these environment variables:
// - OTEL_EXPORTER_OTLP_ENDPOINT
// - OTEL_EXPORTER_OTLP_HEADERS
// - OTEL_EXPORTER_OTLP_TIMEOUT
let exporter = SpanExporter::builder()
.with_tonic()
.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
.build()
.expect("Failed to create span exporter");
// Resource::builder() includes SDK telemetry attributes automatically
let resource = Resource::builder()
.with_service_name(service_name)
.build();
SdkTracerProvider::builder()
.with_resource(resource)
.with_batch_exporter(exporter)
.build()
}
Then initialize it in main():
let tracer_provider = init_tracer();
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
Call tracer_provider.shutdown() before your process exits to flush buffered spans.
Step 4. Run your application
Build your container image and deploy it to your Kubernetes cluster.
The steps above configure the trace exporter but does not auto-instrument anything out of the box. You can either add framework instrumentation for automatic spans from libraries like Axum, Actix Web, and reqwest, or use manual instrumentation to create custom spans, attributes, and events.
Step 1. Set environment variables
Pass environment variables at runtime using docker run:
docker run -e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
-e OTEL_SERVICE_NAME="<service-name>" \
your-image:latest
Or add them to your Dockerfile:
ENV OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
ENV OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
ENV OTEL_SERVICE_NAME="<service-name>"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)
Step 2. Add OpenTelemetry dependencies
Add the following to your Cargo.toml under [dependencies]:
opentelemetry = { version = "0.31.0", features = ["trace"] }
opentelemetry_sdk = { version = "0.31.0", features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace", "tls-roots"] }
tokio = { version = "1", features = ["full"] }
tonic = { version = "0.14", features = ["tls-native-roots"] }
Step 3. Create the tracer initialization
Add the following imports and function to your main.rs:
use opentelemetry_otlp::{SpanExporter, WithTonicConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
fn init_tracer() -> SdkTracerProvider {
let service_name = std::env::var("OTEL_SERVICE_NAME")
.unwrap_or_else(|_| "rust-app".to_string());
// The SDK automatically reads these environment variables:
// - OTEL_EXPORTER_OTLP_ENDPOINT
// - OTEL_EXPORTER_OTLP_HEADERS
// - OTEL_EXPORTER_OTLP_TIMEOUT
let exporter = SpanExporter::builder()
.with_tonic()
.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
.build()
.expect("Failed to create span exporter");
// Resource::builder() includes SDK telemetry attributes automatically
let resource = Resource::builder()
.with_service_name(service_name)
.build();
SdkTracerProvider::builder()
.with_resource(resource)
.with_batch_exporter(exporter)
.build()
}
Then initialize it in main():
let tracer_provider = init_tracer();
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
Call tracer_provider.shutdown() before your process exits to flush buffered spans.
Step 4. Run your application
FROM rust:1.75 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY /app/target/release/your-app .
CMD ["./your-app"]
docker build -t your-image:latest .
docker run -e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
-e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
-e OTEL_SERVICE_NAME="<service-name>" \
your-image:latest
The steps above configure the trace exporter but does not auto-instrument anything out of the box. You can either add framework instrumentation for automatic spans from libraries like Axum, Actix Web, and reqwest, or use manual instrumentation to create custom spans, attributes, and events.
Step 1. Set environment variables (PowerShell)
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_SERVICE_NAME = "<service-name>"
Verify these values:
<region>: Your SigNoz Cloud region<your-ingestion-key>: Your SigNoz ingestion key<service-name>: A descriptive name for your service (e.g.,payment-service)
Step 2. Add OpenTelemetry dependencies
Add the following to your Cargo.toml under [dependencies]:
opentelemetry = { version = "0.31.0", features = ["trace"] }
opentelemetry_sdk = { version = "0.31.0", features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace", "tls-roots"] }
tokio = { version = "1", features = ["full"] }
tonic = { version = "0.14", features = ["tls-native-roots"] }
Step 3. Create the tracer initialization
Add the following imports and function to your main.rs:
use opentelemetry_otlp::{SpanExporter, WithTonicConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
fn init_tracer() -> SdkTracerProvider {
let service_name = std::env::var("OTEL_SERVICE_NAME")
.unwrap_or_else(|_| "rust-app".to_string());
// The SDK automatically reads these environment variables:
// - OTEL_EXPORTER_OTLP_ENDPOINT
// - OTEL_EXPORTER_OTLP_HEADERS
// - OTEL_EXPORTER_OTLP_TIMEOUT
let exporter = SpanExporter::builder()
.with_tonic()
.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots())
.build()
.expect("Failed to create span exporter");
// Resource::builder() includes SDK telemetry attributes automatically
let resource = Resource::builder()
.with_service_name(service_name)
.build();
SdkTracerProvider::builder()
.with_resource(resource)
.with_batch_exporter(exporter)
.build()
}
Then initialize it in main():
let tracer_provider = init_tracer();
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
Call tracer_provider.shutdown() before your process exits to flush buffered spans.
Step 4. Run your application
cargo run
The steps above configure the trace exporter but does not auto-instrument anything out of the box. You can either add framework instrumentation for automatic spans from libraries like Axum, Actix Web, and reqwest, or use manual instrumentation to create custom spans, attributes, and events.
Framework instrumentation
The setup above configures OTLP export for your application. If you use tracing, reqwest, Axum, or Actix Web, add one of the integrations below to capture spans automatically.
All four integrations below emit tracing spans. Add this bridge once to your Cargo.toml and startup code:
tracing-subscriber = { version = "0.3.20", features = ["fmt"] }
tracing-opentelemetry = "0.32.0"
use opentelemetry::trace::TracerProvider;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let tracer = tracer_provider.tracer("my-app");
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(tracing_opentelemetry::layer().with_tracer(tracer))
.init();
Tracing
Core tracing
Additional dependency
tracing = "0.1.41"
Minimal setup
use tracing::instrument;
#[instrument(skip_all, fields(order.id = %order_id))]
async fn process_order(order_id: &str) {
validate_order(order_id).await;
charge_payment(order_id).await;
}
View OpenTelemetry bridge on crates.io
Validate
With your application running, verify traces are being sent to SigNoz:
- Trigger an action in your app that generates a trace. Execute the instrumented code path a few times.
- In SigNoz, open the Services tab and click Refresh. Your application should appear.
- Go to the Traces tab to see your application's traces.
Troubleshooting
Why don't traces appear in SigNoz?
Check environment variables are set:
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_SERVICE_NAME
Verify your ingestion key is correct:
echo $OTEL_EXPORTER_OTLP_HEADERS
Check for TLS errors:
Ensure you have the tls-roots feature enabled in opentelemetry-otlp for HTTPS endpoints:
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace", "tls-roots"] }
Why do OTLP exports fail with connection refused?
- VM: Verify the endpoint URL and that your firewall allows outbound HTTPS on port 443
- Kubernetes: Ensure the endpoint is reachable from within your pod
- Self-hosted: Confirm the collector is listening on the expected port (default: 4317 for gRPC)
Why are spans not being exported?
Ensure you call tracer_provider.shutdown() before your application exits. The batch exporter buffers spans and only sends them periodically or on shutdown:
// At the end of main()
tracer_provider.shutdown().expect("Failed to shutdown tracer provider");
Why do I see failed to create span exporter errors?
This usually indicates a dependency version mismatch. Ensure all OpenTelemetry crates use compatible versions:
opentelemetry = "0.31.0"
opentelemetry_sdk = "0.31.0"
opentelemetry-otlp = "0.31.0"
Setup OpenTelemetry Collector (Optional)
What is the OpenTelemetry Collector?
Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.
Why use it?
- Cleaning up data — Filter out noisy traces you don't care about, or remove sensitive info before it leaves your servers.
- Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
- Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
- Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.
Using the Collector
When using a collector, update your environment variables to point to the collector endpoint:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_SERVICE_NAME="<service-name>"
# No ingestion key needed when using a collector
You'll also need to remove the tls-roots feature if connecting over HTTP:
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "trace"] }
See Switch from direct export to Collector for step-by-step instructions.
For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.
Next Steps
- Send Metrics from your Rust application to correlate traces with metrics for better observability.
- Need to create custom spans or add attributes yourself? Use the Manual Instrumentation in Rust guide once the base setup is in place.
- Set up alerts for your Rust application