/*! The [`Runtime`] type. Runtimes combine components into a fully encapsulated diagnostic pipeline. Each runtime includes: - An [`Emitter`] to receive diagnostic events. - A [`Filter`] to limit the volume of diagnostic events. - A [`Ctxt`] to capture and attach ambient state to events. - A [`Clock`] to timestamp events. - A [`Rng`] to generate correlation ids for events. Runtimes are fully isolated and may be short-lived. A [`Runtime`] can be treated generically, or erased behind an [`AmbientSlot`] for global sharing. This module defines two global runtimes; the [`shared()`] runtime, and the [`internal()`] runtime. Applications should emit their events through the [`shared()`] runtime. Code running within a runtime itself, such as an implementation of [`Emitter`] should emit their events through the [`internal()`] runtime. The [`internal()`] runtime can only be initialized with components that also satisfy internal versions of their regular traits. These marker traits require a component not produce any diagnostics of its own, and so are safe to use by another runtime. If components in the [`internal()`] runtime could produce their own diagnostics then it could cause loops and stack overflows. If an application is initializing both the [`shared()`] and [`internal()`] runtimes, then it should initialize the [`internal()`] runtime _first_. */ use crate::{ clock::Clock, ctxt::Ctxt, emitter::Emitter, empty::Empty, event::ToEvent, filter::Filter, props::Props, rng::Rng, timestamp::Timestamp, }; #[cfg(feature = "implicit_rt")] static SHARED: AmbientSlot = AmbientSlot::new(); #[cfg(feature = "implicit_internal_rt")] static INTERNAL: AmbientInternalSlot = AmbientInternalSlot::new(); /** The global shared runtime for applications to use. This runtime needs to be initialized through its [`shared_slot()`], otherwise it will use [`Empty`] implementations of its components. */ #[cfg(feature = "implicit_rt")] pub fn shared() -> &'static AmbientRuntime<'static> { SHARED.get() } /** The initialization slot for the [`shared()`] runtime. */ #[cfg(feature = "implicit_rt")] pub fn shared_slot() -> &'static AmbientSlot { &SHARED } /** The internal runtime for other runtime components to use. Applications should use the [`shared()`] runtime instead of this one. This runtime can be initialized through its [`internal_slot()`] to enable diagnostics on the regular diagnostics runtime itself. */ #[cfg(feature = "implicit_internal_rt")] pub fn internal() -> &'static AmbientRuntime<'static> { INTERNAL.get() } /** The initialization slot for the [`internal()`] runtime. This slot should be initialized _before_ the [`shared_slot()`] if it's in use. */ #[cfg(feature = "implicit_internal_rt")] pub fn internal_slot() -> &'static AmbientInternalSlot { &INTERNAL } /** A diagnostic pipeline. Each runtime includes the following components: - An [`Emitter`] to receive diagnostic events. - A [`Filter`] to limit the volume of diagnostic events. - A [`Ctxt`] to capture and attach ambient state to events. - A [`Clock`] to timestamp events. - A [`Rng`] to generate correlation ids for events. The components of a runtime can be accessed directly through methods. A runtime can be treated like a builder to set its components, or initialized with them all directly. In statics, you can also use the [`AmbientSlot`] type to hold a type-erased runtime. It's also reasonable to store a fully generic runtime in a static too. */ #[derive(Debug, Clone, Copy)] pub struct Runtime { pub(crate) emitter: TEmitter, pub(crate) filter: TFilter, pub(crate) ctxt: TCtxt, pub(crate) clock: TClock, pub(crate) rng: TRng, } impl Default for Runtime { fn default() -> Self { Runtime::new() } } impl Runtime { /** Create a new, empty runtime. */ pub const fn new() -> Runtime { Runtime { emitter: Empty, filter: Empty, ctxt: Empty, clock: Empty, rng: Empty, } } } impl Runtime { /** Create a new runtime with the given components. */ pub const fn build( emitter: TEmitter, filter: TFilter, ctxt: TCtxt, clock: TClock, rng: TRng, ) -> Self { Runtime { emitter, filter, ctxt, clock, rng, } } /** Get the [`Emitter`]. */ pub const fn emitter(&self) -> &TEmitter { &self.emitter } /** Set the [`Emitter`]. */ pub fn with_emitter(self, emitter: U) -> Runtime { self.map_emitter(|_| emitter) } /** Map the current [`Emitter`] to a new value. */ pub fn map_emitter( self, emitter: impl FnOnce(TEmitter) -> U, ) -> Runtime { Runtime { emitter: emitter(self.emitter), filter: self.filter, ctxt: self.ctxt, clock: self.clock, rng: self.rng, } } /** Get the [`Filter`]. */ pub const fn filter(&self) -> &TFilter { &self.filter } /** Set the [`Filter`]. */ pub fn with_filter(self, filter: U) -> Runtime { self.map_filter(|_| filter) } /** Map the current [`Filter`] to a new value. */ pub fn map_filter( self, filter: impl FnOnce(TFilter) -> U, ) -> Runtime { Runtime { emitter: self.emitter, filter: filter(self.filter), ctxt: self.ctxt, clock: self.clock, rng: self.rng, } } /** Get the [`Ctxt`]. */ pub const fn ctxt(&self) -> &TCtxt { &self.ctxt } /** Set the [`Ctxt`]. */ pub fn with_ctxt(self, ctxt: U) -> Runtime { self.map_ctxt(|_| ctxt) } /** Map the current [`Ctxt`] to a new value. */ pub fn map_ctxt( self, ctxt: impl FnOnce(TCtxt) -> U, ) -> Runtime { Runtime { emitter: self.emitter, filter: self.filter, ctxt: ctxt(self.ctxt), clock: self.clock, rng: self.rng, } } /** Get the [`Clock`]. */ pub const fn clock(&self) -> &TClock { &self.clock } /** Set the [`Clock`]. */ pub fn with_clock(self, clock: U) -> Runtime { self.map_clock(|_| clock) } /** Map the current [`Clock`] to a new value. */ pub fn map_clock( self, clock: impl FnOnce(TClock) -> U, ) -> Runtime { Runtime { emitter: self.emitter, filter: self.filter, ctxt: self.ctxt, clock: clock(self.clock), rng: self.rng, } } /** Get the [`Rng`]. */ pub const fn rng(&self) -> &TRng { &self.rng } /** Set the [`Rng`]. */ pub fn with_rng(self, id_gen: U) -> Runtime { self.map_rng(|_| id_gen) } /** Map the current [`Rng`] to a new value. */ pub fn map_rng( self, id_gen: impl FnOnce(TRng) -> U, ) -> Runtime { Runtime { emitter: self.emitter, filter: self.filter, ctxt: self.ctxt, clock: self.clock, rng: id_gen(self.rng), } } } impl Runtime { /** Emit a diagnostic event through the runtime. This method uses the components of the runtime to process the event. It will: 1. Attempt to assign an extent to the event using [`Clock::now`] if the event doesn't already have one. 2. Add [`Ctxt::Current`] to the event properties. 3. Ensure the event passes [`Filter::matches`]. 4. Emit the event through [`Emitter::emit`]. You can bypass any of these steps by emitting the event directly through the runtime's [`Emitter`]. */ pub fn emit(&self, evt: E) { crate::emit(&self.emitter, &self.filter, &self.ctxt, &self.clock, evt) } } impl Emitter for Runtime { fn emit(&self, evt: E) { self.emit(evt) } fn blocking_flush(&self, timeout: core::time::Duration) -> bool { self.emitter.blocking_flush(timeout) } } /** A marker trait for an [`Emitter`] that does not emit any diagnostics of its own. */ pub trait InternalEmitter: Emitter {} impl InternalEmitter for AssertInternal {} impl InternalEmitter for Empty {} impl InternalEmitter for crate::and::And {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalEmitter> InternalEmitter for alloc::boxed::Box {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalEmitter> InternalEmitter for alloc::sync::Arc {} /** A marker trait for a [`Filter`] that does not emit any diagnostics of its own. */ pub trait InternalFilter: Filter {} impl InternalFilter for AssertInternal {} impl InternalFilter for Empty {} impl InternalFilter for crate::and::And {} impl InternalFilter for crate::or::Or {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalFilter> InternalFilter for alloc::boxed::Box {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalFilter> InternalFilter for alloc::sync::Arc {} /** A marker trait for a [`Ctxt`] that does not emit any diagnostics of its own. */ pub trait InternalCtxt: Ctxt {} impl InternalCtxt for AssertInternal {} impl InternalCtxt for Empty {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalCtxt> InternalCtxt for alloc::boxed::Box {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalCtxt> InternalCtxt for alloc::sync::Arc {} /** A marker trait for a [`Clock`] that does not emit any diagnostics of its own. */ pub trait InternalClock: Clock {} impl InternalClock for AssertInternal {} impl InternalClock for Empty {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalClock> InternalClock for alloc::boxed::Box {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalClock> InternalClock for alloc::sync::Arc {} /** A marker trait for an [`Rng`] that does not emit any diagnostics of its own. */ pub trait InternalRng: Rng {} impl InternalRng for AssertInternal {} impl InternalRng for Empty {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalRng> InternalRng for alloc::boxed::Box {} #[cfg(feature = "alloc")] impl<'a, T: ?Sized + InternalRng> InternalRng for alloc::sync::Arc {} /** Assert that a given component does not emit any diagnostics of its own. */ pub struct AssertInternal(pub T); impl Emitter for AssertInternal { fn emit(&self, evt: E) { self.0.emit(evt) } fn blocking_flush(&self, timeout: core::time::Duration) -> bool { self.0.blocking_flush(timeout) } } impl Filter for AssertInternal { fn matches(&self, evt: E) -> bool { self.0.matches(evt) } } impl Ctxt for AssertInternal { type Current = T::Current; type Frame = T::Frame; fn open_root(&self, props: P) -> Self::Frame { self.0.open_root(props) } fn open_push(&self, props: P) -> Self::Frame { self.0.open_push(props) } fn enter(&self, local: &mut Self::Frame) { self.0.enter(local) } fn with_current R>(&self, with: F) -> R { self.0.with_current(with) } fn exit(&self, local: &mut Self::Frame) { self.0.exit(local) } fn close(&self, frame: Self::Frame) { self.0.close(frame) } } impl Clock for AssertInternal { fn now(&self) -> Option { self.0.now() } } impl Rng for AssertInternal { fn fill>(&self, arr: A) -> Option { self.0.fill(arr) } fn gen_u64(&self) -> Option { self.0.gen_u64() } fn gen_u128(&self) -> Option { self.0.gen_u128() } } #[cfg(feature = "std")] mod std_support { use alloc::boxed::Box; use core::any::Any; use std::sync::OnceLock; use crate::{ clock::ErasedClock, ctxt::ErasedCtxt, emitter::ErasedEmitter, filter::ErasedFilter, rng::ErasedRng, }; use super::*; /** A type-erased [`Emitter`] for an [`AmbientSlot`]. */ pub type AmbientEmitter<'a> = &'a (dyn ErasedEmitter + Send + Sync + 'static); trait AnyEmitter: Any + ErasedEmitter + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_super(&self) -> &(dyn ErasedEmitter + Send + Sync + 'static); } impl AnyEmitter for T { fn as_any(&self) -> &dyn Any { self } fn as_super(&self) -> &(dyn ErasedEmitter + Send + Sync + 'static) { self } } /** A type-erased [`Filter`] for an [`AmbientSlot`]. */ pub type AmbientFilter<'a> = &'a (dyn ErasedFilter + Send + Sync + 'static); trait AnyFilter: Any + ErasedFilter + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_super(&self) -> &(dyn ErasedFilter + Send + Sync + 'static); } impl AnyFilter for T { fn as_any(&self) -> &dyn Any { self } fn as_super(&self) -> &(dyn ErasedFilter + Send + Sync + 'static) { self } } /** A type-erased [`Ctxt`] for an [`AmbientSlot`]. */ pub type AmbientCtxt<'a> = &'a (dyn ErasedCtxt + Send + Sync + 'static); trait AnyCtxt: Any + ErasedCtxt + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_super(&self) -> &(dyn ErasedCtxt + Send + Sync + 'static); } impl AnyCtxt for T { fn as_any(&self) -> &dyn Any { self } fn as_super(&self) -> &(dyn ErasedCtxt + Send + Sync + 'static) { self } } /** A type-erased [`Clock`] for an [`AmbientSlot`]. */ pub type AmbientClock<'a> = &'a (dyn ErasedClock + Send + Sync + 'static); trait AnyClock: Any + ErasedClock + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_super(&self) -> &(dyn ErasedClock + Send + Sync + 'static); } impl AnyClock for T { fn as_any(&self) -> &dyn Any { self } fn as_super(&self) -> &(dyn ErasedClock + Send + Sync + 'static) { self } } /** A type-erased [`Rng`] for an [`AmbientSlot`]. */ pub type AmbientRng<'a> = &'a (dyn ErasedRng + Send + Sync + 'static); trait AnyRng: Any + ErasedRng + Send + Sync + 'static { fn as_any(&self) -> &dyn Any; fn as_super(&self) -> &(dyn ErasedRng + Send + Sync + 'static); } impl AnyRng for T { fn as_any(&self) -> &dyn Any { self } fn as_super(&self) -> &(dyn ErasedRng + Send + Sync + 'static) { self } } /** A type-erased slot for a globally shared [`Runtime`]. The slot is suitable to store directly in a static; it coordinates its own initialization using a [`OnceLock`]. */ pub struct AmbientSlot(OnceLock); /** A type-erased slot for the [`internal()`] runtime. */ #[cfg(feature = "implicit_internal_rt")] pub struct AmbientInternalSlot(AmbientSlot); struct AmbientSync { value: AmbientSyncValue, runtime: AmbientSyncRuntime, } type AmbientSyncValue = Runtime< Box, Box, Box, Box, Box, >; type AmbientSyncRuntime = Runtime< *const (dyn ErasedEmitter + Send + Sync), *const (dyn ErasedFilter + Send + Sync), *const (dyn ErasedCtxt + Send + Sync), *const (dyn ErasedClock + Send + Sync), *const (dyn ErasedRng + Send + Sync), >; /** A type-erased [`Runtime`]. */ pub type AmbientRuntime<'a> = Runtime< AmbientEmitter<'a>, AmbientFilter<'a>, AmbientCtxt<'a>, AmbientClock<'a>, AmbientRng<'a>, >; unsafe impl Send for AmbientSync where AmbientSyncValue: Send {} unsafe impl Sync for AmbientSync where AmbientSyncValue: Sync {} impl AmbientSlot { /** Create a new, empty slot. */ pub const fn new() -> Self { AmbientSlot(OnceLock::new()) } /** Whether the slot has been initialized with a runtime. */ pub fn is_enabled(&self) -> bool { self.0.get().is_some() } /** Try initialize the slot with the given components. If the slot has not already been initialized then the components will be installed and a reference to the resulting [`Runtime`] will be returned. If the slot has already been initialized by another caller then this method will discard the components and return `None`. */ pub fn init( &self, pipeline: Runtime, ) -> Option> where TEmitter: Emitter + Send + Sync + 'static, TFilter: Filter + Send + Sync + 'static, TCtxt: Ctxt + Send + Sync + 'static, TCtxt::Frame: Send + 'static, TClock: Clock + Send + Sync + 'static, TRng: Rng + Send + Sync + 'static, { self.0 .set({ let value = pipeline .map_emitter(|emitter| { Box::new(emitter) as Box }) .map_filter(|filter| Box::new(filter) as Box) .map_ctxt(|ctxt| Box::new(ctxt) as Box) .map_clock(|clock| Box::new(clock) as Box) .map_rng(|id_gen| Box::new(id_gen) as Box); let runtime = Runtime::build( value.emitter().as_super() as *const _, value.filter().as_super() as *const _, value.ctxt().as_super() as *const _, value.clock().as_super() as *const _, value.rng().as_super() as *const _, ); AmbientSync { value, runtime } }) .ok()?; let rt = self.0.get()?; Some(Runtime::build( rt.value.emitter().as_any().downcast_ref()?, rt.value.filter().as_any().downcast_ref()?, rt.value.ctxt().as_any().downcast_ref()?, rt.value.clock().as_any().downcast_ref()?, rt.value.rng().as_any().downcast_ref()?, )) } /** Get the underlying [`Runtime`], or a [`Runtime::default`] if it hasn't been initialized yet. */ pub fn get(&self) -> &AmbientRuntime { const EMPTY_AMBIENT_RUNTIME: AmbientRuntime = Runtime::build( &Empty as &(dyn ErasedEmitter + Send + Sync + 'static), &Empty as &(dyn ErasedFilter + Send + Sync + 'static), &Empty as &(dyn ErasedCtxt + Send + Sync + 'static), &Empty as &(dyn ErasedClock + Send + Sync + 'static), &Empty as &(dyn ErasedRng + Send + Sync + 'static), ); self.0 .get() .map(|rt| // SAFETY: The borrow of `self` cannot outlive the components // it contains. This block is converting `*const dyn T + Send + Sync` // to `&'_ dyn T + Send + Sync` unsafe { &*(&rt.runtime as *const AmbientSyncRuntime as *const AmbientRuntime) }) .unwrap_or(&EMPTY_AMBIENT_RUNTIME) } } #[cfg(feature = "implicit_internal_rt")] impl AmbientInternalSlot { pub(in crate::runtime) const fn new() -> Self { AmbientInternalSlot(AmbientSlot(OnceLock::new())) } /** Whether the [`internal()`] runtime has been initialized. Components can use this method to decide whether to do work related to diagnostic capturing. */ pub fn is_enabled(&self) -> bool { self.0.is_enabled() } /** Initialize the [`internal()`] runtime with the given components. The components must satisfy additional trait bounds compared to a regular [`AmbientSlot`]. Each component must also implement a marker trait that promises they don't produce any diagnostics of their own. */ pub fn init( &self, pipeline: Runtime, ) -> Option> where TEmitter: InternalEmitter + Send + Sync + 'static, TFilter: InternalFilter + Send + Sync + 'static, TCtxt: InternalCtxt + Send + Sync + 'static, TCtxt::Frame: Send + 'static, TClock: InternalClock + Send + Sync + 'static, TRng: InternalRng + Send + Sync + 'static, { self.0.init(pipeline) } /** Get the underlying [`Runtime`], or a [`Runtime::default`] if it hasn't been initialized yet. */ pub fn get(&self) -> &AmbientRuntime { self.0.get() } } #[cfg(test)] mod tests { use super::*; #[test] fn ambient_slot_init() { let slot = AmbientSlot::new(); assert!(!slot.is_enabled()); assert!(slot.init(Runtime::new()).is_some()); assert!(slot.is_enabled()); assert!(slot.init(Runtime::new()).is_none()); } } } #[cfg(feature = "std")] pub use self::std_support::*; #[cfg(not(feature = "std"))] mod no_std_support { use super::*; /** A slot for a shared runtime. Without the `std` feature enabled, this slot cannot be initialized. */ pub struct AmbientSlot {} /** A slot for the internal runtime. Without the `std` feature enabled, this slot cannot be initialized. */ #[cfg(feature = "implicit_internal_rt")] pub struct AmbientInternalSlot(AmbientSlot); impl AmbientSlot { /** Create a new, empty slot. */ pub const fn new() -> Self { AmbientSlot {} } /** When the `std` feature is not enabled this method always returns `false`. */ pub fn is_enabled(&self) -> bool { false } /** When the `std` feature is not enabled this method always returns an empty runtime. */ pub fn get(&self) -> &AmbientRuntime { const EMPTY_AMBIENT_RUNTIME: AmbientRuntime = Runtime::build(&Empty, &Empty, &Empty, &Empty, &Empty); &EMPTY_AMBIENT_RUNTIME } } #[cfg(feature = "implicit_internal_rt")] impl AmbientInternalSlot { pub(in crate::runtime) const fn new() -> Self { AmbientInternalSlot(AmbientSlot::new()) } /** When the `std` feature is not enabled this method always returns `false`. */ pub fn is_enabled(&self) -> bool { false } /** When the `std` feature is not enabled this method always returns an empty runtime. */ pub fn get(&self) -> &AmbientRuntime { self.0.get() } } /** When the `std` feature is not enabled this is always [`Empty`]. */ pub type AmbientEmitter<'a> = &'a Empty; /** When the `std` feature is not enabled this is always [`Empty`]. */ pub type AmbientFilter<'a> = &'a Empty; /** When the `std` feature is not enabled this is always [`Empty`]. */ pub type AmbientCtxt<'a> = &'a Empty; /** When the `std` feature is not enabled this is always [`Empty`]. */ pub type AmbientClock<'a> = &'a Empty; /** When the `std` feature is not enabled this is always [`Empty`]. */ pub type AmbientRng<'a> = &'a Empty; /** When the `std` feature is not enabled this is always [`Runtime::default`]. */ pub type AmbientRuntime<'a> = Runtime< AmbientEmitter<'a>, AmbientFilter<'a>, AmbientCtxt<'a>, AmbientClock<'a>, AmbientRng<'a>, >; } #[cfg(not(feature = "std"))] pub use self::no_std_support::*;