Overview
The OTLP gRPC exporter provides a high-performance alternative to HTTP for sending traces, metrics, and logs. gRPC offers better throughput and lower latency, making it ideal for high-volume production environments.
Dependencies
Add these dependencies to your Cargo.toml:
[dependencies]
opentelemetry = { version = "*" }
opentelemetry_sdk = { version = "*", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "*", features = ["grpc-tonic"] }
tonic = "0.12"
tokio = { version = "1", features = ["full"] }
Exporting Traces via gRPC
Configure OTLP gRPC Exporter
Set up the OTLP exporter with gRPC transport using Tonic.use opentelemetry::global;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_otlp::WithExportConfig;
fn init_tracer() -> SdkTracerProvider {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()
.expect("Failed to create OTLP gRPC exporter");
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
.build();
global::set_tracer_provider(provider.clone());
provider
}
Configure TLS and Authentication
Add TLS and metadata for secure connections.use opentelemetry_otlp::WithTonicConfig;
use tonic::metadata::{MetadataMap, MetadataValue};
use tonic::transport::ClientTlsConfig;
let mut metadata = MetadataMap::new();
metadata.insert(
"authorization",
MetadataValue::from_str("Bearer YOUR_API_TOKEN").unwrap(),
);
let tls_config = ClientTlsConfig::new()
.domain_name("your-backend.com");
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("https://your-backend.com:4317")
.with_metadata(metadata)
.with_tls_config(tls_config)
.build()
.expect("Failed to create OTLP gRPC exporter");
Use the Tracer
Create and export spans normally.use opentelemetry::trace::{Tracer, SpanKind};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let tracer_provider = init_tracer();
let tracer = global::tracer("my-service");
let span = tracer
.span_builder("grpc_operation")
.with_kind(SpanKind::Internal)
.start(&tracer);
// Your application logic
tracer_provider.shutdown()?;
Ok(())
}
Exporting Metrics via gRPC
use opentelemetry::global;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_otlp::WithExportConfig;
fn init_metrics() -> SdkMeterProvider {
let exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()
.expect("Failed to create OTLP metrics exporter");
let provider = SdkMeterProvider::builder()
.with_periodic_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
.build();
global::set_meter_provider(provider.clone());
provider
}
Exporting Logs via gRPC
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use tracing_subscriber::prelude::*;
fn init_logs() -> SdkLoggerProvider {
let exporter = opentelemetry_otlp::LogExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()
.expect("Failed to create OTLP log exporter");
let provider = SdkLoggerProvider::builder()
.with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
.build();
let otel_layer = OpenTelemetryTracingBridge::new(&provider);
tracing_subscriber::registry().with(otel_layer).init();
provider
}
gRPC-Specific Configuration
Connection Settings
use std::time::Duration;
use tonic::transport::Channel;
let channel = Channel::from_static("http://localhost:4317")
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(5))
.tcp_keepalive(Some(Duration::from_secs(60)));
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_channel(channel)
.build()?;
Compression
use tonic::codec::CompressionEncoding;
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.with_compression(CompressionEncoding::Gzip)
.build()?;
gRPC compression can significantly reduce network bandwidth usage, especially for high-volume telemetry data.
HTTP vs gRPC Comparison
| Feature | HTTP | gRPC |
|---|
| Performance | Good | Excellent |
| Latency | Higher | Lower |
| Throughput | Good | Better |
| Ease of Debug | Easier (curl, browsers) | Harder (needs gRPC tools) |
| Firewall Friendly | More (port 80/443) | Less (custom ports) |
| Compression | Optional | Built-in, efficient |
| Streaming | No | Yes |
For most production use cases, gRPC is recommended due to better performance. Use HTTP if you have strict firewall requirements or need easier debugging.
Environment Variables
The OTLP gRPC exporter respects standard OpenTelemetry environment variables:
# Endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# Signal-specific endpoints
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4317
# Headers (sent as gRPC metadata)
export OTEL_EXPORTER_OTLP_HEADERS="api-key=secret,x-custom-header=value"
# Protocol (should be 'grpc')
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# Timeout (milliseconds)
export OTEL_EXPORTER_OTLP_TIMEOUT=10000
Common Endpoints
Local OpenTelemetry Collector
// Default gRPC port
.with_endpoint("http://localhost:4317")
Secure Connection
use tonic::transport::ClientTlsConfig;
let tls_config = ClientTlsConfig::new();
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("https://otel-collector.example.com:4317")
.with_tls_config(tls_config)
.build()?;
Complete Example: All Signals
Here’s a complete example exporting traces, metrics, and logs via gRPC:
use opentelemetry::global;
use opentelemetry_sdk::{trace::SdkTracerProvider, metrics::SdkMeterProvider, logs::SdkLoggerProvider};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use tracing_subscriber::prelude::*;
fn init_telemetry() -> (SdkTracerProvider, SdkMeterProvider, SdkLoggerProvider) {
let endpoint = "http://localhost:4317";
// Traces
let trace_exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.expect("Failed to create trace exporter");
let tracer_provider = SdkTracerProvider::builder()
.with_batch_exporter(trace_exporter, opentelemetry_sdk::runtime::Tokio)
.build();
global::set_tracer_provider(tracer_provider.clone());
// Metrics
let metric_exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.expect("Failed to create metric exporter");
let meter_provider = SdkMeterProvider::builder()
.with_periodic_exporter(metric_exporter, opentelemetry_sdk::runtime::Tokio)
.build();
global::set_meter_provider(meter_provider.clone());
// Logs
let log_exporter = opentelemetry_otlp::LogExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.expect("Failed to create log exporter");
let logger_provider = SdkLoggerProvider::builder()
.with_batch_exporter(log_exporter, opentelemetry_sdk::runtime::Tokio)
.build();
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry().with(otel_layer).init();
(tracer_provider, meter_provider, logger_provider)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tracer_provider, meter_provider, logger_provider) = init_telemetry();
// Use telemetry in your application
// Shutdown all providers
tracer_provider.shutdown()?;
meter_provider.shutdown()?;
logger_provider.shutdown()?;
Ok(())
}
Always shutdown all providers in the correct order (traces, metrics, logs) to ensure all telemetry data is flushed before the application exits.