Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix logging with interest cache #2165

Open
wants to merge 19 commits into
base: v0.1.x
Choose a base branch
from
52 changes: 36 additions & 16 deletions tracing-core/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@
//! currently default `Dispatch`. This is used primarily by `tracing`
//! instrumentation.
//!

use crate::{
callsite, span,
callsite,
lazy::Lazy,
span,
subscriber::{self, NoSubscriber, Subscriber},
Event, LevelFilter, Metadata,
};
Expand All @@ -133,14 +136,14 @@ use crate::stdlib::{
any::Any,
fmt,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering},
Arc,
},
};

#[cfg(feature = "std")]
use crate::stdlib::{
cell::{Cell, RefCell, RefMut},
cell::{Cell, RefCell},
error,
sync::Weak,
};
Expand Down Expand Up @@ -168,6 +171,20 @@ const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;

static mut GLOBAL_DISPATCH: Option<Dispatch> = None;
// The global default dispatch that should be used by [`Enter::with_current`]
// when there is no explicit local dispatch set.
//
// By default this is initialized to [`NO_DISPATCH`] and will be set to
// [`GLOBAL_DISPATCH`] when this is being set.
static GLOBAL_DEFAULT_DISPATCH: Lazy<AtomicPtr<Dispatch>> =
Lazy::new(|| AtomicPtr::new(unsafe { &mut *NO_DISPATCH as *mut _ }));

// This is only `mut` to make it work as default value of [`GLOBAL_DEFAULT_DISPATCH`].
//
// We can not use `new` as we don't want this to be registered at the callsites.
static mut NO_DISPATCH: Lazy<Dispatch> = Lazy::new(|| Dispatch {
subscriber: Arc::new(NoSubscriber::default()),
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it occurs to me that we could potentially avoid the need to have two Lazy cells and a static mut if we made a way for a "no dispatcher" Dispatch to be constructed in a const fn. we could investigate changing Dispatch to:

pub struct Dispatch {
    subscriber: Option<Arc<dyn Subscriber + ...>>,
}

and having the None variant always nop. Of course, we'd want to ensure that doesn't have a worse performance impact, but it might be worth considering.

It would also make cloning the NO_DISPATCH dispatcher cheaper, as it wouldn't have to do an Arc refcount bump...


/// The dispatch state of a thread.
#[cfg(feature = "std")]
Expand Down Expand Up @@ -273,6 +290,10 @@ pub fn set_global_default(dispatcher: Dispatch) -> Result<(), SetGlobalDefaultEr
{
unsafe {
GLOBAL_DISPATCH = Some(dispatcher);
GLOBAL_DEFAULT_DISPATCH.store(
GLOBAL_DISPATCH.as_mut().unwrap() as *mut _,
Ordering::SeqCst,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally, i think this would be a compare_exchange so that it fails if the global dispatch value was something other than the initial default NO_DISPATCH value. we could panic if the CAS fails (at least in debug mode)

}
GLOBAL_INIT.store(INITIALIZED, Ordering::SeqCst);
EXISTS.store(true, Ordering::Release);
Expand Down Expand Up @@ -334,12 +355,12 @@ where
CURRENT_STATE
.try_with(|state| {
if let Some(entered) = state.enter() {
return f(&*entered.current());
return entered.with_current(|current| f(current));
}

f(&Dispatch::none())
f(unsafe { &NO_DISPATCH })
})
.unwrap_or_else(|_| f(&Dispatch::none()))
.unwrap_or_else(|_| f(unsafe { &NO_DISPATCH }))
}

/// Executes a closure with a reference to this thread's current [dispatcher].
Expand All @@ -356,7 +377,7 @@ pub fn get_current<T>(f: impl FnOnce(&Dispatch) -> T) -> Option<T> {
CURRENT_STATE
.try_with(|state| {
let entered = state.enter()?;
Some(f(&*entered.current()))
Some(entered.with_current(f))
})
.ok()?
}
Expand All @@ -382,10 +403,11 @@ where
if let Some(d) = get_global() {
f(d)
} else {
f(&Dispatch::none())
f(unsafe { &NO_DISPATCH })
}
}

#[cfg(not(feature = "std"))]
fn get_global() -> Option<&'static Dispatch> {
if GLOBAL_INIT.load(Ordering::SeqCst) != INITIALIZED {
return None;
Expand All @@ -406,9 +428,7 @@ impl Dispatch {
/// Returns a new `Dispatch` that discards events and spans.
#[inline]
pub fn none() -> Self {
Dispatch {
subscriber: Arc::new(NoSubscriber::default()),
}
unsafe { NO_DISPATCH.clone() }
}

/// Returns a `Dispatch` that forwards to the given [`Subscriber`].
Expand Down Expand Up @@ -711,11 +731,11 @@ impl State {
#[cfg(feature = "std")]
impl<'a> Entered<'a> {
#[inline]
fn current(&self) -> RefMut<'a, Dispatch> {
let default = self.0.default.borrow_mut();
RefMut::map(default, |default| {
default.get_or_insert_with(|| get_global().cloned().unwrap_or_else(Dispatch::none))
})
fn with_current<T>(&self, f: impl FnOnce(&Dispatch) -> T) -> T {
match &*self.0.default.borrow() {
Some(default) => f(default),
None => unsafe { f(&*GLOBAL_DEFAULT_DISPATCH.load(Ordering::SeqCst)) },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure this load needs to be sequentially consistent, or can it just be an Acquire load? what other atomic value does this need to synchronize with?

}
}
}

Expand Down
101 changes: 101 additions & 0 deletions tracing-log/tests/init_interests_cache_before_global_subscriber.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#![cfg(feature = "interest-cache")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test isn't run as part of the CI. Should I add it to the CI config?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there should be a CI job that runs with --all-features for every crate.


use std::sync::{Arc, Mutex};
use tracing::subscriber::set_global_default;
use tracing_core::span::{Attributes, Record};
use tracing_core::{span, Event, Level, LevelFilter, Metadata, Subscriber};
use tracing_log::{LogTracer, NormalizeEvent};

struct State {
last_normalized_metadata: Mutex<(bool, Option<OwnedMetadata>)>,
}

#[derive(PartialEq, Debug)]
struct OwnedMetadata {
name: String,
target: String,
level: Level,
module_path: Option<String>,
file: Option<String>,
line: Option<u32>,
}

struct TestSubscriber(Arc<State>);

impl Subscriber for TestSubscriber {
fn enabled(&self, meta: &Metadata<'_>) -> bool {
dbg!(meta);
true
}

fn max_level_hint(&self) -> Option<LevelFilter> {
Some(LevelFilter::from_level(Level::INFO))
}

fn new_span(&self, _span: &Attributes<'_>) -> span::Id {
span::Id::from_u64(42)
}

fn record(&self, _span: &span::Id, _values: &Record<'_>) {}

fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}

fn event(&self, event: &Event<'_>) {
dbg!(event);
*self.0.last_normalized_metadata.lock().unwrap() = (
event.is_log(),
event.normalized_metadata().map(|normalized| OwnedMetadata {
name: normalized.name().to_string(),
target: normalized.target().to_string(),
level: *normalized.level(),
module_path: normalized.module_path().map(String::from),
file: normalized.file().map(String::from),
line: normalized.line(),
}),
)
}

fn enter(&self, _span: &span::Id) {}

fn exit(&self, _span: &span::Id) {}
}

#[test]
fn interests_cache_works() {
LogTracer::builder()
.with_interest_cache(tracing_log::InterestCacheConfig::default())
.init()
.unwrap();
let me = Arc::new(State {
last_normalized_metadata: Mutex::new((false, None)),
});
let state = me.clone();

set_global_default(TestSubscriber(me)).unwrap();

let log = log::Record::builder()
.args(format_args!("Error!"))
.level(log::Level::Info)
.build();
log::logger().log(&log);
last(
&state,
true,
Some(OwnedMetadata {
name: "log event".to_string(),
target: "".to_string(),
level: Level::INFO,
module_path: None,
file: None,
line: None,
}),
);
}

fn last(state: &State, should_be_log: bool, expected: Option<OwnedMetadata>) {
let lock = state.last_normalized_metadata.lock().unwrap();
let (is_log, metadata) = &*lock;
dbg!(&metadata);
assert_eq!(dbg!(*is_log), should_be_log);
assert_eq!(metadata.as_ref(), expected.as_ref());
}