Tracing with axum

I am trying to implement telemetry for my API in axum and for my express server, using OpenTelemetry and Jaeger. I am attempting in every way to configure the tracings so that they are displayed in Jaeger along with the tracings from express, so that when express makes a request to rust, they are in the same trace.

#[derive(Clone)]
pub struct TraceIngestLayer;

impl TraceIngestLayer {
    pub fn new() -> Self {
        Self {}
    }
}

impl<S> tower::Layer<S> for TraceIngestLayer {
    type Service = TraceIngest<S>;

    fn layer(&self, inner: S) -> Self::Service {
        TraceIngest::new(inner)
    }
}

#[derive(Clone, Copy)]
pub struct TraceIngest<S> {
    inner: S,
}

impl<S> TraceIngest<S> {
    pub fn new(inner: S) -> Self {
        Self { inner }
    }
}

impl<S> Service<Request<Body>> for TraceIngest<S>
where
    S: tower::Service<Request<Body>> + Clone + Send + 'static,
    S::Future: Send,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(
        &mut self,
        _cx: &mut std::task::Context<'_>,
    ) -> Poll<
        std::result::Result<
            (),
            <S as tower::Service<opentelemetry_http::Request<Body>>>::Error,
        >,
    > {
        std::task::Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: Request<Body>) -> Self::Future {
        let parent_cx = get_parent_context(&req);

        let mut span = global::tracer("opentelemetry-otlp")
            .start_with_context(req.uri().to_string(), &parent_cx);
        
        span.set_attribute(KeyValue::new("http.method", req.method().to_string()));

        self.inner.call(req)
    }
}

fn get_parent_context(req: &Request<Body>) -> Context {
    global::get_text_map_propagator(|propagator| {
        propagator.extract(&HeaderExtractor(req.headers()))
    })
}
fn init_tracer() -> Result<Tracer> {
    global::set_text_map_propagator(TraceContextPropagator::new());

    let provider = TracerProvider::builder()
        .with_simple_exporter(SpanExporter::default())
        .build();

    global::set_tracer_provider(provider);

    let mut resource = Resource::new(vec![KeyValue::new(
        "servopa",
        "tracing-jaeger",
    )]);

    let tracer = opentelemetry_otlp::new_pipeline()
        .tracing()
        .with_exporter(
            opentelemetry_otlp::new_exporter()
                .tonic()
                .with_endpoint(&config().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT),
        )
        .with_trace_config(
            sdkConfig()
                .with_resource(resource)
                .with_sampler(sdktrace::Sampler::AlwaysOn),
        )
        .install_batch(runtime::Tokio)?;

    Ok(tracer)
}

#[tokio::main]
async fn main() -> Result<()> {
    let tracer = init_tracer()?;

    let filter_layer = EnvFilter::try_from_default_env()
        .or_else(|_| EnvFilter::try_new("info"))
        .unwrap();

    let fmt_layer = fmt::layer().compact();

    let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);

    tracing_subscriber::registry()
        .with(filter_layer)
        .with(fmt_layer)
        .with(otel_layer)
        .init();
        
        ...

The documentation on this is scarce... The first trace is my TraceIngestLayer and the second is my functions when I use tracing:instrument . Even these traces areseparated, I don't understand...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.