diff --git a/src/lib.rs b/src/lib.rs index a2e4ae2..52ae9a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -467,57 +467,7 @@ Hello, World! ## Custom runtimes -All functionality in `emit` is based on a [`runtime::Runtime`]. When you call [`Setup::init`], it initializes the [`runtime::shared`] runtime for you, which is also what macros use by default. - -You can implement your own runtime, providing your own implementations of the ambient clock, randomness, and global context. First, disable the default features of `emit` in your `Cargo.toml`: - -```toml -[dependencies.emit] -version = "*" -default-features = false -features = ["std"] -``` - -This will ensure the `rt` control parameter is always passed to macros so that your custom runtime will always be used. Next, define your runtime itself and use it in macros: - -``` -// Define a static runtime to use -// In this example, we use the default implementations of most things, -// but you can also bring-your-own -static RUNTIME: emit::runtime::Runtime< - MyEmitter, - emit::Empty, - emit::platform::thread_local_ctxt::ThreadLocalCtxt, - emit::platform::system_clock::SystemClock, - emit::platform::rand_rng::RandRng, -> = emit::runtime::Runtime::build( - MyEmitter, - emit::Empty, - emit::platform::thread_local_ctxt::ThreadLocalCtxt::shared(), - emit::platform::system_clock::SystemClock::new(), - emit::platform::rand_rng::RandRng::new(), -); - -struct MyEmitter; - -impl emit::Emitter for MyEmitter { - fn emit(&self, evt: E) { - println!("{}", evt.to_event().msg()); - } - - fn blocking_flush(&self, _: std::time::Duration) -> bool { - // Nothing to flush - true - } -} - -// Use your runtime with the `rt` control parameter -emit::emit!(rt: &RUNTIME, "emitted through a custom runtime"); -``` - -```text -emitted through a custom runtime -``` +Everything in `emit` is based on a [`runtime::Runtime`]; a fully isolated set of components that provide capabilities like clocks and randomness, as well as your configured emitters and filters. When a runtime isn't specified, it's [`runtime::shared`]. You can define your own runtimes too. The [`mod@setup`] module has more details. ## Troubleshooting diff --git a/src/metric.rs b/src/metric.rs index 9ab5297..6cf0fe2 100644 --- a/src/metric.rs +++ b/src/metric.rs @@ -247,6 +247,9 @@ use emit_core::{ use crate::kind::Kind; +/** +A diagnostic event that represents a metric sample. +*/ pub struct Metric<'a, P> { module: Path<'a>, extent: Option, @@ -402,6 +405,9 @@ impl<'a, P: Props> Props for Metric<'a, P> { } } +/** +A source of [`Metric`]s. +*/ pub trait Source { fn sample_metrics(&self, sampler: S); @@ -429,7 +435,7 @@ pub trait Source { where Self: Sized + Clone + Send + Sync + 'static, { - reporter.source(self.clone()); + reporter.add_source(self.clone()); self } @@ -511,6 +517,9 @@ mod alloc_support { use alloc::{boxed::Box, vec::Vec}; + /** + A set of [`Source`]s that are all sampled together. + */ pub struct Reporter(Vec>); impl Reporter { @@ -518,7 +527,7 @@ mod alloc_support { Reporter(Vec::new()) } - pub fn source(&mut self, source: impl Source + Send + Sync + 'static) -> &mut Self { + pub fn add_source(&mut self, source: impl Source + Send + Sync + 'static) -> &mut Self { self.0.push(Box::new(source)); self @@ -600,10 +609,17 @@ impl<'a> Source for dyn ErasedSource + Send + Sync + 'a { } pub mod sampler { + /*! + The [`Sampler`] type. + */ + use emit_core::empty::Empty; use super::*; + /** + A receiver of [`Metric`]s as produced by a [`Source`]. + */ pub trait Sampler { fn metric(&self, metric: &Metric

); } diff --git a/src/platform.rs b/src/platform.rs index a04ff10..f77e17e 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -1,3 +1,9 @@ +/*! +Components provided by the underlying platform. + +This module defines implementations of [`crate::runtime::Runtime`] components that use capabilities of the host platform. +*/ + #[cfg(feature = "std")] use emit_core::{clock::ErasedClock, rng::ErasedRng, runtime::AssertInternal}; @@ -19,6 +25,9 @@ type DefaultIdGen = rand_rng::RandRng; #[cfg(feature = "std")] pub(crate) type DefaultCtxt = thread_local_ctxt::ThreadLocalCtxt; +/** +A type-erased container for system services used when intiailizing runtimes. +*/ pub(crate) struct Platform { #[cfg(feature = "std")] pub(crate) clock: AssertInternal>, diff --git a/src/platform/rand_rng.rs b/src/platform/rand_rng.rs index f8f7dbf..7045078 100644 --- a/src/platform/rand_rng.rs +++ b/src/platform/rand_rng.rs @@ -1,10 +1,20 @@ +/*! +The [`RandRng`] type. +*/ + use emit_core::{rng::Rng, runtime::InternalRng}; use rand::{Rng as _, RngCore}; +/** +An [`Rng`] based on the [`rand`] library. +*/ #[derive(Default, Debug, Clone, Copy)] pub struct RandRng {} impl RandRng { + /** + Create a new source of randomness. + */ pub const fn new() -> Self { RandRng {} } diff --git a/src/platform/system_clock.rs b/src/platform/system_clock.rs index 7407519..38da37b 100644 --- a/src/platform/system_clock.rs +++ b/src/platform/system_clock.rs @@ -1,9 +1,19 @@ +/*! +The [`SystemClock`] type. +*/ + use emit_core::{clock::Clock, runtime::InternalClock, timestamp::Timestamp}; +/** +A [`Clock`] based on the standard library's [`std::time::SystemTime`]. +*/ #[derive(Default, Debug, Clone, Copy)] pub struct SystemClock {} impl SystemClock { + /** + Create a new clock. + */ pub const fn new() -> Self { SystemClock {} } diff --git a/src/platform/thread_local_ctxt.rs b/src/platform/thread_local_ctxt.rs index 2bd187f..969b7e9 100644 --- a/src/platform/thread_local_ctxt.rs +++ b/src/platform/thread_local_ctxt.rs @@ -1,3 +1,7 @@ +/*! +The [`ThreadLocalCtxt`] type. +*/ + use core::mem; use std::{cell::RefCell, collections::HashMap, ops::ControlFlow, sync::Mutex}; @@ -10,43 +14,11 @@ use emit_core::{ value::{OwnedValue, Value}, }; -// Start this id from 1 so it doesn't intersect with the `shared` variant below -static NEXT_CTXT_ID: Mutex = Mutex::new(1); - -fn ctxt_id() -> usize { - let mut next_id = NEXT_CTXT_ID.lock().unwrap(); - let id = *next_id; - *next_id = id.wrapping_add(1); - - id -} - -thread_local! { - static ACTIVE: RefCell> = RefCell::new(HashMap::new()); -} - -fn current(id: usize) -> ThreadLocalSpan { - ACTIVE.with(|active| { - active - .borrow_mut() - .entry(id) - .or_insert_with(|| ThreadLocalSpan { props: None }) - .clone() - }) -} - -fn swap(id: usize, incoming: &mut ThreadLocalSpan) { - ACTIVE.with(|active| { - let mut active = active.borrow_mut(); - - let current = active - .entry(id) - .or_insert_with(|| ThreadLocalSpan { props: None }); - - mem::swap(current, incoming); - }) -} +/** +A [`Ctxt`] that stores ambient state in thread local storage. +Frames fully encapsulate all properties that were active when they were created so can be sent across threads to move that state with them. +*/ #[derive(Debug, Clone, Copy)] pub struct ThreadLocalCtxt { id: usize, @@ -59,21 +31,30 @@ impl Default for ThreadLocalCtxt { } impl ThreadLocalCtxt { + /** + Create a new thread local store with fully isolated storage. + */ pub fn new() -> Self { ThreadLocalCtxt { id: ctxt_id() } } + /** + Create a new thread local store sharing the same storage as any other [`ThreadLocalCtxt::shared`]. + */ pub const fn shared() -> Self { ThreadLocalCtxt { id: 0 } } } +/** +A [`Ctxt::Frame`] on a [`ThreadLocalCtxt`]. +*/ #[derive(Clone)] -pub struct ThreadLocalSpan { +pub struct ThreadLocalCtxtFrame { props: Option, OwnedValue>>>, } -impl Props for ThreadLocalSpan { +impl Props for ThreadLocalCtxtFrame { fn for_each<'a, F: FnMut(Str<'a>, Value<'a>) -> ControlFlow<()>>( &'a self, mut for_each: F, @@ -87,14 +68,18 @@ impl Props for ThreadLocalSpan { ControlFlow::Continue(()) } + fn get<'v, K: emit_core::str::ToStr>(&'v self, key: K) -> Option> { + self.props.as_ref().and_then(|props| props.get(key)) + } + fn is_unique(&self) -> bool { true } } impl Ctxt for ThreadLocalCtxt { - type Current = ThreadLocalSpan; - type Frame = ThreadLocalSpan; + type Current = ThreadLocalCtxtFrame; + type Frame = ThreadLocalCtxtFrame; fn with_current R>(&self, with: F) -> R { let current = current(self.id); @@ -109,7 +94,7 @@ impl Ctxt for ThreadLocalCtxt { ControlFlow::Continue(()) }); - ThreadLocalSpan { + ThreadLocalCtxtFrame { props: Some(Arc::new(span)), } } @@ -143,3 +128,40 @@ impl Ctxt for ThreadLocalCtxt { } impl InternalCtxt for ThreadLocalCtxt {} + +// Start this id from 1 so it doesn't intersect with the `shared` variant below +static NEXT_CTXT_ID: Mutex = Mutex::new(1); + +fn ctxt_id() -> usize { + let mut next_id = NEXT_CTXT_ID.lock().unwrap(); + let id = *next_id; + *next_id = id.wrapping_add(1); + + id +} + +thread_local! { + static ACTIVE: RefCell> = RefCell::new(HashMap::new()); +} + +fn current(id: usize) -> ThreadLocalCtxtFrame { + ACTIVE.with(|active| { + active + .borrow_mut() + .entry(id) + .or_insert_with(|| ThreadLocalCtxtFrame { props: None }) + .clone() + }) +} + +fn swap(id: usize, incoming: &mut ThreadLocalCtxtFrame) { + ACTIVE.with(|active| { + let mut active = active.borrow_mut(); + + let current = active + .entry(id) + .or_insert_with(|| ThreadLocalCtxtFrame { props: None }); + + mem::swap(current, incoming); + }) +} diff --git a/src/setup.rs b/src/setup.rs index 6b2a88c..1364993 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,3 +1,83 @@ +/*! +The [`Setup`] type. + +All functionality in `emit` is based on a [`runtime::Runtime`]. When you call [`Setup::init`], it initializes the [`runtime::shared`] runtime for you, which is also what macros use by default. + +You can implement your own runtime, providing your own implementations of the ambient clock, randomness, and global context. First, disable the default features of `emit` in your `Cargo.toml`: + +```toml +[dependencies.emit] +version = "*" +default-features = false +features = ["std"] +``` + +This will ensure the `rt` control parameter is always passed to macros so that your custom runtime will always be used. + +You can define your runtime as a [`crate::runtime::AmbientSlot`] in a static and initialize it through [`Setup::init_slot`]: + +``` +// Define a static runtime to use +// In this example, we use the default implementations of most things, +// but you can also bring-your-own +static RUNTIME: emit::runtime::AmbientSlot = emit::runtime::AmbientSlot::new(); + +let rt = emit::setup() + .emit_to(emit::emitter::from_fn(|evt| println!("{}", evt.msg()))) + .init_slot(&RUNTIME); + +// Use your runtime with the `rt` control parameter +emit::emit!(rt: RUNTIME.get(), "emitted through a custom runtime"); + +rt.blocking_flush(std::time::Duration::from_secs(5)); +``` + +```text +emitted through a custom runtime +``` + +The [`crate::runtime::AmbientSlot`] is type-erased, but you can also define your own fully concrete runtimes too: + +``` +// Define a static runtime to use +// In this example, we use the default implementations of most things, +// but you can also bring-your-own +static RUNTIME: emit::runtime::Runtime< + MyEmitter, + emit::Empty, + emit::platform::thread_local_ctxt::ThreadLocalCtxt, + emit::platform::system_clock::SystemClock, + emit::platform::rand_rng::RandRng, +> = emit::runtime::Runtime::build( + MyEmitter, + emit::Empty, + emit::platform::thread_local_ctxt::ThreadLocalCtxt::shared(), + emit::platform::system_clock::SystemClock::new(), + emit::platform::rand_rng::RandRng::new(), +); + +struct MyEmitter; + +impl emit::Emitter for MyEmitter { + fn emit(&self, evt: E) { + println!("{}", evt.to_event().msg()); + } + + fn blocking_flush(&self, _: std::time::Duration) -> bool { + // Nothing to flush + true + } +} + +// Use your runtime with the `rt` control parameter +emit::emit!(rt: &RUNTIME, "emitted through a custom runtime"); +``` + +```text +emitted through a custom runtime +``` +*/ + use core::time::Duration; use emit_core::{ @@ -11,6 +91,11 @@ use emit_core::{ use crate::platform::{DefaultCtxt, Platform}; +/** +Configure `emit` with [`Emitter`]s, [`Filter`]s, and [`Ctxt`]. + +This function should be called as early in your application as possible. It returns a [`Setup`] builder that, once configured, can be initialized with a call to [`Setup::init`]. +*/ pub fn setup() -> Setup { Setup::default() } @@ -18,6 +103,10 @@ pub fn setup() -> Setup { type DefaultEmitter = Empty; type DefaultFilter = Empty; +/** +A configuration builder for an `emit` runtime. +*/ +#[must_use = "call `.init()` to finish setup"] pub struct Setup { emitter: TEmitter, filter: TFilter, @@ -32,6 +121,9 @@ impl Default for Setup { } impl Setup { + /** + Create a new builder with the default [`Emitter`], [`Filter`], and [`Ctxt`]. + */ pub fn new() -> Self { Setup { emitter: Default::default(), @@ -43,7 +135,9 @@ impl Setup { } impl Setup { - #[must_use = "call `.init()` to finish setup"] + /** + Set the [`Emitter`] that will receive diagnostic events. + */ pub fn emit_to(self, emitter: UEmitter) -> Setup { Setup { emitter, @@ -53,7 +147,9 @@ impl Setup( self, emitter: UEmitter, @@ -66,7 +162,9 @@ impl Setup( self, map: impl FnOnce(TEmitter) -> UEmitter, @@ -79,7 +177,9 @@ impl Setup(self, filter: UFilter) -> Setup { Setup { emitter: self.emitter, @@ -89,7 +189,9 @@ impl Setup(self, ctxt: UCtxt) -> Setup { Setup { emitter: self.emitter, @@ -99,7 +201,9 @@ impl Setup( self, map: impl FnOnce(TCtxt) -> UCtxt, @@ -121,11 +225,19 @@ impl< where TCtxt::Frame: Send + 'static, { + /** + Initialize the default runtime used by `emit` macros. + + This method initializes [`crate::runtime::shared`]. + */ #[must_use = "call `blocking_flush` at the end of `main` to ensure events are flushed."] pub fn init(self) -> Init<&'static TEmitter, &'static TCtxt> { self.init_slot(emit_core::runtime::shared_slot()) } + /** + Initialize a runtime in the given static `slot`. + */ #[must_use = "call `blocking_flush` at the end of `main` to ensure events are flushed."] pub fn init_slot( self, @@ -157,6 +269,11 @@ impl< where TCtxt::Frame: Send + 'static, { + /** + Initialize the internal runtime used for diagnosing runtimes themselves. + + This method initializes [`crate::runtime::internal`]. + */ #[must_use = "call `blocking_flush` at the end of `main` (after flushing the main runtime) to ensure events are flushed."] pub fn init_internal(self) -> Init<&'static TEmitter, &'static TCtxt> { let ambient = emit_core::runtime::internal_slot() @@ -177,21 +294,37 @@ where } } +/** +The result of calling [`Setup::init`]. + +This type is a handle to an initialized runtime that can be used to ensure it's fully flushed with a call to [`Init::blocking_flush`] before your application exits. +*/ pub struct Init { emitter: TEmitter, ctxt: TCtxt, } impl Init { + /** + Get a reference to the initialized [`Emitter`]. + */ pub fn emitter(&self) -> &TEmitter { &self.emitter } + /** + Get a reference to the initialized [`Ctxt`]. + */ pub fn ctxt(&self) -> &TCtxt { &self.ctxt } - pub fn blocking_flush(&self, timeout: Duration) { - self.emitter.blocking_flush(timeout); + /** + Flush the runtime, ensuring all diagnostic events are fully processed. + + This method forwards to [`Emitter::blocking_flush`], which has details on how the timeout is handled. + */ + pub fn blocking_flush(&self, timeout: Duration) -> bool { + self.emitter.blocking_flush(timeout) } } diff --git a/test/smoke-test/main.rs b/test/smoke-test/main.rs index e9c37a8..498d807 100644 --- a/test/smoke-test/main.rs +++ b/test/smoke-test/main.rs @@ -54,7 +54,7 @@ async fn main() { .spawn() .unwrap(); - reporter.source(otlp.metric_source()); + reporter.add_source(otlp.metric_source()); otlp }) @@ -67,7 +67,7 @@ async fn main() { .spawn() .unwrap(); - reporter.source(file_set.metric_source()); + reporter.add_source(file_set.metric_source()); file_set }))