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...