revoke-trace

Crates.iorevoke-trace
lib.rsrevoke-trace
version0.3.0
created_at2025-07-13 06:02:16.431409+00
updated_at2025-07-13 06:02:16.431409+00
descriptionDistributed tracing with OpenTelemetry for Revoke framework
homepage
repositoryhttps://github.com/revoke/revoke
max_upload_size
id1750021
size162,037
LioRael (LioRael)

documentation

README

revoke-trace

Distributed tracing module for the Revoke microservices framework, providing OpenTelemetry-based observability.

Features

  • OpenTelemetry Native: Built on OpenTelemetry standards
  • Multiple Exporters: Support for OTLP, Jaeger, Zipkin, and Prometheus
  • Automatic Propagation: W3C Trace Context propagation
  • Span Enrichment: Automatic span attributes and events
  • Performance: Minimal overhead with sampling support
  • Integration: Easy integration with popular frameworks

Installation

Add to your Cargo.toml:

[dependencies]
revoke-trace = { version = "0.1.0" }

Quick Start

use revoke_trace::{init_tracer, Tracer};
use opentelemetry::trace::{Tracer as _, Span};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracer
    let tracer = init_tracer("my-service").await?;
    
    // Create a span
    let mut span = tracer.start("process-request");
    span.set_attribute("user.id", "user-123");
    
    // Do work
    process_request().await?;
    
    // End span
    span.end();
    
    Ok(())
}

Configuration

Basic Configuration

use revoke_trace::{TracerConfig, OtlpExporter};

let config = TracerConfig::builder()
    .service_name("my-service")
    .service_version("1.0.0")
    .otlp_endpoint("http://localhost:4317")
    .sample_rate(0.1)  // Sample 10% of traces
    .build();

let tracer = config.init().await?;

Advanced Configuration

use revoke_trace::{TracerConfig, BatchConfig, Resource};
use opentelemetry::KeyValue;

let config = TracerConfig::builder()
    .service_name("my-service")
    .with_resource(Resource::new(vec![
        KeyValue::new("service.namespace", "production"),
        KeyValue::new("service.instance.id", "instance-1"),
        KeyValue::new("deployment.environment", "prod"),
    ]))
    .with_batch_config(BatchConfig {
        max_queue_size: 2048,
        max_export_batch_size: 512,
        max_export_timeout: Duration::from_secs(30),
        scheduled_delay: Duration::from_secs(5),
    })
    .with_sampler(Sampler::TraceIdRatio(0.1))
    .build();

Usage Patterns

Manual Instrumentation

use revoke_trace::global;
use opentelemetry::trace::{Tracer, SpanKind, Status};

let tracer = global::tracer("my-component");

// Create span with options
let span = tracer
    .span_builder("database-query")
    .with_kind(SpanKind::Client)
    .with_attributes(vec![
        KeyValue::new("db.system", "postgresql"),
        KeyValue::new("db.statement", "SELECT * FROM users"),
    ])
    .start(&tracer);

// Set span as active
let _guard = span.enter();

// Add events
span.add_event(
    "query.executed",
    vec![KeyValue::new("rows.count", 42)],
);

// Set status
span.set_status(Status::Ok);

Async Context Propagation

use revoke_trace::{FutureExt, Context};

// Automatically propagate context
async fn process_request(ctx: Context) {
    // Spawn task with context
    tokio::spawn(
        async move {
            do_background_work().await;
        }
        .with_context(ctx.clone())
    );
    
    // Call another service with context
    call_service()
        .with_context(ctx)
        .await?;
}

Error Handling

use revoke_trace::SpanExt;

let span = tracer.start("operation");

match do_operation().await {
    Ok(result) => {
        span.set_status(Status::Ok);
        result
    }
    Err(e) => {
        span.record_error(&e);
        span.set_status(Status::error(e.to_string()));
        return Err(e);
    }
}

Framework Integration

Axum Integration

use revoke_trace::axum::{TraceLayer, trace_extractor};
use axum::{Router, routing::get};

let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new("http-server"));

async fn handler(trace: trace_extractor::Trace) -> &'static str {
    let span = trace.span();
    span.set_attribute("custom.attribute", "value");
    
    "Hello, World!"
}

Tonic (gRPC) Integration

use revoke_trace::tonic::{TraceInterceptor, TraceService};
use tonic::transport::Server;

let service = MyService::default();
let traced_service = TraceService::new(service);

Server::builder()
    .add_service(
        MyServiceServer::with_interceptor(
            traced_service,
            TraceInterceptor::new()
        )
    )
    .serve(addr)
    .await?;

Database Tracing

use revoke_trace::database::{trace_query, ConnectionTracer};

// Trace individual queries
let result = trace_query("SELECT * FROM users", || {
    sqlx::query("SELECT * FROM users")
        .fetch_all(&pool)
        .await
}).await?;

// Trace connection pool
let pool = sqlx::PgPoolOptions::new()
    .after_connect(|conn| {
        ConnectionTracer::new().instrument(conn)
    })
    .connect(&database_url)
    .await?;

Sampling

Sampling Strategies

use revoke_trace::sampling::{Sampler, ParentBased, TraceIdRatio};

// Always sample
let sampler = Sampler::AlwaysOn;

// Never sample
let sampler = Sampler::AlwaysOff;

// Sample 10% of traces
let sampler = Sampler::TraceIdRatio(0.1);

// Parent-based sampling
let sampler = Sampler::ParentBased(Box::new(
    TraceIdRatio::new(0.1)
));

// Custom sampler
struct CustomSampler;

impl ShouldSample for CustomSampler {
    fn should_sample(
        &self,
        parent_context: Option<&Context>,
        trace_id: TraceId,
        name: &str,
        span_kind: &SpanKind,
        attributes: &[KeyValue],
    ) -> SamplingResult {
        // Custom logic
        if name.contains("critical") {
            SamplingResult::RecordAndSample
        } else {
            SamplingResult::Drop
        }
    }
}

Span Enrichment

Automatic Enrichment

use revoke_trace::enrichment::{SpanEnricher, StandardEnricher};

let enricher = StandardEnricher::new()
    .with_process_info()      // PID, executable name
    .with_host_info()         // Hostname, IP
    .with_runtime_info()      // Rust version, OS
    .with_environment_info(); // Environment variables

tracer.set_span_enricher(enricher);

Custom Enrichment

use revoke_trace::enrichment::SpanEnricher;

struct TenantEnricher {
    tenant_id: String,
}

impl SpanEnricher for TenantEnricher {
    fn enrich(&self, span: &mut Span) {
        span.set_attribute("tenant.id", self.tenant_id.clone());
        span.set_attribute("region", detect_region());
    }
}

Metrics Integration

use revoke_trace::metrics::{TracingMetrics, MetricsExporter};

// Enable trace metrics
let metrics = TracingMetrics::new()
    .with_span_duration_histogram()
    .with_span_count_counter()
    .with_error_rate_gauge();

// Export to Prometheus
let exporter = MetricsExporter::prometheus()
    .with_endpoint("http://localhost:9090")
    .build();

metrics.set_exporter(exporter);

Performance Optimization

Batch Processing

use revoke_trace::batch::{BatchSpanProcessor, BatchConfig};

let batch_config = BatchConfig {
    max_queue_size: 2048,
    max_export_batch_size: 512,
    scheduled_delay: Duration::from_secs(5),
    max_export_timeout: Duration::from_secs(30),
};

let processor = BatchSpanProcessor::new(
    exporter,
    batch_config,
);

Span Limits

use revoke_trace::limits::SpanLimits;

let limits = SpanLimits::builder()
    .with_max_attributes_per_span(128)
    .with_max_events_per_span(128)
    .with_max_links_per_span(128)
    .with_max_attribute_length(1024)
    .build();

tracer.set_span_limits(limits);

Context Propagation

HTTP Headers

use revoke_trace::propagation::{Propagator, TraceContextPropagator};
use http::HeaderMap;

let propagator = TraceContextPropagator::new();

// Inject context into headers
let mut headers = HeaderMap::new();
propagator.inject(&context, &mut headers);

// Extract context from headers
let context = propagator.extract(&headers);

Cross-Service Propagation

use revoke_trace::propagation::global;

// Set global propagator
global::set_text_map_propagator(TraceContextPropagator::new());

// In HTTP client
let client = reqwest::Client::new();
let mut request = client.get("http://other-service/api");

// Inject current context
global::get_text_map_propagator(|propagator| {
    propagator.inject_context(&mut request);
});

let response = request.send().await?;

Debugging and Testing

Trace Debugging

use revoke_trace::debug::{ConsoleExporter, DebugTracer};

// Console exporter for development
let tracer = DebugTracer::new()
    .with_console_exporter()
    .with_pretty_print()
    .build();

// In-memory exporter for testing
let (tracer, receiver) = DebugTracer::new()
    .with_memory_exporter()
    .build();

// Check captured spans
let spans = receiver.try_recv()?;
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].name, "test-span");

Testing Utilities

use revoke_trace::testing::{TestTracer, SpanAssertion};

#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_tracing() {
        let (tracer, spans) = TestTracer::new();
        
        // Run code under test
        my_function().await;
        
        // Assert spans
        spans.assert()
            .span_exists("process-request")
            .has_attribute("user.id", "123")
            .has_status(Status::Ok);
    }
}

Best Practices

  1. Span Naming: Use descriptive, consistent names (e.g., service.operation)
  2. Attributes: Add relevant attributes but avoid sensitive data
  3. Sampling: Use appropriate sampling rates for production
  4. Context: Always propagate context across service boundaries
  5. Errors: Record errors with stack traces when available
  6. Performance: Use batch processing and sampling to minimize overhead
  7. Security: Never include passwords, tokens, or PII in traces

Examples

See the examples directory:

  • basic_tracing.rs - Simple tracing setup
  • http_server.rs - HTTP server with tracing
  • grpc_service.rs - gRPC service with tracing
  • context_propagation.rs - Cross-service context propagation
  • custom_exporter.rs - Custom trace exporter implementation
Commit count: 0

cargo fmt