#2874·tracing

在 v0.1 中,错误的 `Interest::Never` 被缓存,且永远不会被置为无效

作者: plietar创建于 2024年2月8日更新于 2026年9月16日

Bug Report

Version

tracing-core 0.1.32. The master branch does not have the relevant optimization and is probably fine.

Platform

x86_64 Linux

Crates

tracing-core

Description

Under the right set of circumstances, it is possible for all events and spans to be silenced, regardless of the subscriber's configuration. The particular sequence of events is as follows:

  1. A Dispatch object is created, wrapping any kind of subscriber (an fmt subscriber with maximum logging will do). The object must not be set as the global or thread-local dispatcher yet.
  2. A tracing macro is called. Since no dispatcher is configured yet, nothing is printed, as expected.
  3. The dispatch object from step 1 is registered as a thread-local dispatcher, using tracing::dispatcher::with_default
  4. The same line of code as step 2 is executed again. One would expect this to print, since a dispatcher is now configured, but instead nothing happens again. The following piece of code demonstrates the bug in a deterministic way. You can achieve a similar result using only the higher level tracing::subscriber::with_default API, but that requires multiple threads and a bit of luck to get the right order of operations.
rust
fn is_enabled() -> bool {
    tracing::info!("log me maybe");
    return tracing::enabled!(tracing::Level::DEBUG);
}

fn main() {
    let subscriber = tracing_subscriber::fmt()
        .with_max_level(tracing::Level::TRACE)
        .finish();
    let dispatch = tracing::Dispatch::new(subscriber);

    // This returns false and doesn't print anything, as expected
    is_enabled();

    tracing::dispatcher::with_default(&dispatch, || {
        // This should have returned true, but does not.
        assert!(is_enabled());
    });
}

I've tracked the bug down to an optimization in tracing-core's callsite module, specifically the has_just_one atomic boolean.

  • At step 1, a dispatcher is created and registered using the Dispatchers::register_dispatch method. Since it is the first one to be registered, the has_just_one flag is set to true.
  • At step 2, the call site of the tracing macro needs to be registered, using DefaultCallsite::register. In that process, the callsite's interest is calculated by rebuild_callsite_interest. DefaultCallsite::register had created and passed in a Rebuilder object, and based on the value of has_just_one, a Rebuilder::JustOne is used.
  • The rebuild_callsite_interest function calls Rebuilder::for_each to

内容来源: tokio-rs/tracing