# 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`:
```toml
[dependencies]
revoke-trace = { version = "0.1.0" }
```
## Quick Start
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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](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