From beb9b3963e256a140820bd749e1da473276a6e3e Mon Sep 17 00:00:00 2001 From: Rasmus Rendal Date: Tue, 16 Jun 2026 23:40:29 +0100 Subject: [PATCH] fix: Disable tracing for crates that cause stack overflows on exit by default Due to https://github.com/open-telemetry/opentelemetry-rust/issues/2877, we have to turn off tracing for a couple of crates, to avoid a stack overflow when exiting a binary using opentelemetry-rust and an OTLP endpoint. I don't love this fix personally, but the alternative seems worse. --- Cargo.toml | 1 + src/config.rs | 13 ++++++++++++- tests/otlp.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/otlp.rs diff --git a/Cargo.toml b/Cargo.toml index 1231fb2..04afd0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ tower-otel-http-metrics = { version = "0.16.0", features = [ opentelemetry-resource-detectors = "0.9.0" [dev-dependencies] +axum = { version = "^0.8" } tokio = { version = "1.43.0", features = ["full"] } [features] diff --git a/src/config.rs b/src/config.rs index c5b3ff8..bc12499 100644 --- a/src/config.rs +++ b/src/config.rs @@ -168,7 +168,18 @@ impl Default for ProviderConfig { enabled: false, level: default_level_filter(), general_level: default_level_filter(), - dependencies_levels: HashMap::new(), + // We have to ignore these specific crates to avoid them emitting traces + // after our OTLP collector has been turned off, causing an infinite loop. + // + // See the issue: https://github.com/open-telemetry/opentelemetry-rust/issues/2877 + // See the source of the workaround: + // https://github.com/open-telemetry/opentelemetry-rust/blob/v0.31.0/opentelemetry-otlp/examples/basic-otlp/src/main.rs#L69-L86 + dependencies_levels: HashMap::from([ + ("hyper".to_owned(), LevelFilter(tracing::level_filters::LevelFilter::OFF)), + ("tonic".to_owned(), LevelFilter(tracing::level_filters::LevelFilter::OFF)), + ("h2".to_owned(), LevelFilter(tracing::level_filters::LevelFilter::OFF)), + ("reqwest".to_owned(), LevelFilter(tracing::level_filters::LevelFilter::OFF)), + ]), } } } diff --git a/tests/otlp.rs b/tests/otlp.rs new file mode 100644 index 0000000..52112cb --- /dev/null +++ b/tests/otlp.rs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2025 Famedly GmbH (info@famedly.com) +// +// SPDX-License-Identifier: Apache-2.0 + +//! Tests for OTLP + +#![allow(clippy::expect_used)] + +use axum::routing::post; +use rust_telemetry::{ + config::{ExporterConfig, OtelConfig, StdoutLogsConfig}, + init_otel, +}; +use url::Url; + +/// The current version of the OTLP exporter also induces traces after it's been +/// shut down, causing a feedback loop which ends in a stack overflow +/// +/// This test checks that we can start rust-telemetry with an OTLP endpoint +/// enabled, output a log line, and exit our program without a stack overflow. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_otlp_exporter() { + let router = post(|| async { Vec::new() }); + let listener = + tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("Could not bind TCPListener"); + let endpoint = Url::parse(&format!( + "http://{}", + listener.local_addr().expect("Failed getting address for listener") + )) + .expect("Failed parsing URL for endpoint"); + let _mock_otlp = tokio::spawn(async move { axum::serve(listener, router).await }); + + let provider_config = Some(rust_telemetry::config::ProviderConfig { + enabled: true, + general_level: famedly_rust_utils::LevelFilter(tracing::level_filters::LevelFilter::DEBUG), + ..Default::default() + }); + + let config = OtelConfig { + stdout: Some(StdoutLogsConfig { enabled: true, ..Default::default() }), + exporter: Some(ExporterConfig { + endpoint: endpoint.into(), + logs: provider_config.clone(), + traces: provider_config.clone(), + ..Default::default() + }), + }; + let _guard = init_otel!(&config).expect("Error initializing Otel"); + tracing::info!("Output a line"); +}