arcly-http 0.1.0

Enterprise-grade NestJS-inspired web framework on axum: zero-lock DI, declarative controllers, multi-tenant data routing, transactional outbox, ABAC, and a self-documenting OpenAPI surface
Documentation
//! OpenTelemetry OTLP tracer — init once at launch, read globally.
//!
//! Call `init_tracer` from `ArclyObservabilityPlugin::on_init`.
//! Call `opentelemetry::global::shutdown_tracer_provider()` in `on_shutdown`
//! to flush any buffered spans before the process exits.

use opentelemetry::trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState};
use opentelemetry::Context;

use crate::web::context::RequestContext;

/// Configuration for the OTLP span exporter.
pub struct OtelConfig {
    pub service_name: &'static str,
    pub service_version: &'static str,
    /// gRPC endpoint of the OTLP collector, e.g. `"http://localhost:4317"`.
    pub otlp_endpoint: &'static str,
}

/// Initialise the global OpenTelemetry tracer provider with a batch OTLP
/// gRPC exporter. Safe to call once — subsequent calls on the same process
/// are no-ops because `set_tracer_provider` is idempotent on the global.
pub fn init_tracer(cfg: &OtelConfig) {
    use opentelemetry::KeyValue;
    use opentelemetry_otlp::WithExportConfig;
    use opentelemetry_sdk::trace::TracerProvider;
    use opentelemetry_sdk::Resource;

    let resource = Resource::new(vec![
        KeyValue::new("service.name", cfg.service_name),
        KeyValue::new("service.version", cfg.service_version),
    ]);

    let exporter_result = opentelemetry_otlp::SpanExporter::builder()
        .with_tonic()
        .with_endpoint(cfg.otlp_endpoint)
        .build();

    match exporter_result {
        Ok(exporter) => {
            let provider = TracerProvider::builder()
                .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
                .with_resource(resource)
                .build();

            opentelemetry::global::set_tracer_provider(provider);

            tracing::info!(
                endpoint = cfg.otlp_endpoint,
                service = cfg.service_name,
                "OTLP tracer initialised"
            );
        }
        Err(e) => {
            tracing::warn!(
                error    = %e,
                endpoint = cfg.otlp_endpoint,
                "OTLP tracer failed to initialise — spans will not be exported"
            );
        }
    }
}

/// Build an OpenTelemetry `Context` that represents the *parent* of the span
/// about to be created for this request.
///
/// If the incoming request carried a valid W3C `traceparent` header (already
/// parsed into `ctx.trace_id()` + `ctx.parent_span_id()`), the returned
/// context wraps a remote `SpanContext` so the new span is linked as a child
/// of the upstream span. For root spans (no `traceparent`), the current
/// ambient context is returned unchanged.
pub fn parent_context_from(ctx: &RequestContext) -> Context {
    let parent_span_id = match ctx.parent_span_id() {
        Some(bytes) => SpanId::from_bytes(bytes),
        None => return Context::current(), // root span — no parent to link
    };

    let span_context = SpanContext::new(
        TraceId::from_bytes(ctx.trace_id()),
        parent_span_id,
        TraceFlags::SAMPLED,
        true, // is_remote
        TraceState::default(),
    );
    Context::current().with_remote_span_context(span_context)
}