I am in the process of crafting a subscriber using Tokio tracing for a particular application. This application also relies on several external crates like Hyper, reqwest, and gRPC, which might be instrumented to utilize tokio tracing. Further, this subscriber can be using some external crates which might be instrumented for tokio tracing. I aim to filter out logs generated by these external libraries from being exported via the subscriber I am building.
To achieve this, I attempted to employ a task-local variable to establish a suppression flag. The idea was to proceed with logging only if the suppression flag remains inactive. However, this approach doesn't seem to be effective. The issue appears to stem from the flag not being accessible across all asynchronous tasks spawned within the contextual flow of an existing async task, as illustrated in the example below.
/*
[dependencies]
hyper = { version = "0.14.7", features = [ "full" ] }
tokio = { version = "1.33.0", features = ["full"] }
tracing = "0.1.25"
tracing-core = "0.1"
pin-project = "1.1.3"
tracing-subscriber = "0.3.17"
*/
use hyper::Client;
use hyper::Uri;
use std::future::Future;
use tracing::info;
use tracing::Event;
use tracing::Metadata;
use tracing::Subscriber;
use tracing_core::span::Id;
use std::pin::Pin;
use std::task::{Context, Poll};
use pin_project::pin_project;
use tracing_core::span::Record;
struct SimpleSubscriber;
impl Subscriber for SimpleSubscriber {
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
!is_logging_suppressed()
}
fn new_span(&self, _: &tracing::span::Attributes<'_>) -> Id {
Id::from_u64(0)
}
fn event(&self, event: &Event<'_>) {
println!(
"[{}] - {}",
event.metadata().level(),
event.metadata().target()
);
}
fn record(&self, _: &Id, _: &Record<'_>) {}
fn record_follows_from(&self, _: &Id, _: &Id) {}
fn enter(&self, _: &Id) {}
fn exit(&self, _: &Id) {}
}
// Define a task-local variable
tokio::task_local! {
static SUPPRESSED: bool;
}
#[pin_project]
struct SuppressLogging<F> {
#[pin]
inner: F,
}
impl<F: Future> Future for SuppressLogging<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
this.inner.poll(cx)
}
}
fn is_logging_suppressed() -> bool {
SUPPRESSED.with(|val| *val)
}
async fn process_request() {
info!("This log shouldn't be suppressed");
let suppressed_future = SUPPRESSED.scope(true, async {
// all logs/traces from Hyper crates should be suppressed
let client = Client::new();
let uri = "http://httpbin.org/ip".parse::<Uri>().unwrap();
let response = client.get(uri).await.unwrap();
let body = hyper::body::to_bytes(response).await.unwrap();
println!("body: {:?}", body);
});
let suppress_logging_future = SuppressLogging {
inner: suppressed_future,
};
suppress_logging_future.await;
info!("This log also shouldn't be suppressed");
}
async fn main_function() {
SUPPRESSED.scope(false, process_request()).await;
}
#[tokio::main]
async fn main() {
let subscriber = SimpleSubscriber;
tracing::subscriber::set_global_default(subscriber)
.expect("Setting default subscriber failed.");
main_function().await;
}
The example fails with error:
[INFO] - async1
thread 'tokio-runtime-worker' panicked at 'cannot access a task-local storage value without setting it first', src/main.rs:66:16
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
However, if I remove all the suppression logic from above code, it will write all the logs from Hyper crate, which is what I am trying to prevent.
Note - Looking for a generalized solution. Hyper is used just as an example.