-
Notifications
You must be signed in to change notification settings - Fork 663
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
base: v0.1.x
Are you sure you want to change the base?
Changes from 9 commits
8c06e78
694f6f8
41e0147
3255715
834a633
58e1bb9
45c477e
b6d6ff7
857f740
65122c7
89dafca
34cb851
a6321bd
5d40c4a
0575fcf
c8a8a28
609e9a9
86bd7b4
ccdacb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
}; | ||
|
@@ -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, | ||
}; | ||
|
@@ -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()), | ||
}); | ||
|
||
/// The dispatch state of a thread. | ||
#[cfg(feature = "std")] | ||
|
@@ -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, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ideally, i think this would be a |
||
} | ||
GLOBAL_INIT.store(INITIALIZED, Ordering::SeqCst); | ||
EXISTS.store(true, Ordering::Release); | ||
|
@@ -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]. | ||
|
@@ -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()? | ||
} | ||
|
@@ -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; | ||
|
@@ -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`]. | ||
|
@@ -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)) }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#![cfg(feature = "interest-cache")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there should be a CI job that runs with |
||
|
||
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()); | ||
} |
There was a problem hiding this comment.
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 astatic mut
if we made a way for a "no dispatcher"Dispatch
to be constructed in aconst fn
. we could investigate changingDispatch
to: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 anArc
refcount bump...