diff --git a/Cargo.toml b/Cargo.toml index 11b5133..b6052f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ opentelemetry = { version = "0.32.0", default-features = false } opentelemetry-otlp = { version = "0.32.0", default-features = false } opentelemetry_sdk = { version = "0.32.0", default-features = false } pin-project = { version = "1.1.10" } +pollster = { version = "0.4.0" } rand = { version = "0.10.1" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } diff --git a/appenders/file/src/append.rs b/appenders/file/src/append.rs index 06e56c5..f328fa9 100644 --- a/appenders/file/src/append.rs +++ b/appenders/file/src/append.rs @@ -32,7 +32,7 @@ use crate::rotation::Rotation; /// A builder to configure and create an [`File`] appender. /// -/// See [module-level documentation](super) for usage examples. +/// See the [crate documentation](super) for usage examples. #[derive(Debug)] pub struct FileBuilder { builder: RollingFileWriterBuilder, diff --git a/appenders/syslog/src/lib.rs b/appenders/syslog/src/lib.rs index c095a36..3440935 100644 --- a/appenders/syslog/src/lib.rs +++ b/appenders/syslog/src/lib.rs @@ -273,7 +273,7 @@ mod unix_ext { /// An appender that writes log records to syslog. /// -/// See [module-level documentation](self) for usage examples. +/// See the [crate documentation](self) for usage examples. #[derive(Debug)] pub struct Syslog { sender: Mutex, diff --git a/core/src/kv.rs b/core/src/kv.rs index eb1ff5b..4447f2b 100644 --- a/core/src/kv.rs +++ b/core/src/kv.rs @@ -30,8 +30,17 @@ pub trait Visitor { fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error>; } +impl Visitor for F +where + F: FnMut(KeyView, ValueView) -> Result<(), Error>, +{ + fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error> { + self(key, value) + } +} + /// A key in a key-value pair. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Key<'a>(RefStr<'a>); impl fmt::Display for Key<'_> { @@ -80,6 +89,19 @@ impl Key<'_> { #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct KeyOwned(Cow<'static, str>); +macro_rules! impl_key_owned_from { + ($ty:ty) => { + impl From<$ty> for KeyOwned { + fn from(v: $ty) -> Self { + KeyOwned(Cow::from(v)) + } + } + }; +} + +impl_key_owned_from!(&'static str); +impl_key_owned_from!(String); + impl Borrow for KeyOwned { fn borrow(&self) -> &str { &self.0 @@ -117,7 +139,7 @@ impl KeyOwned { } /// A borrowed view of a key in a key-value pair. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct KeyView<'a>(RefStr<'a>); impl Borrow for KeyView<'_> { @@ -341,10 +363,10 @@ impl<'a> Iterator for MapValueIter<'a> { } /// A borrowed value in a key-value pair. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Value<'a>(ValueState<'a>); -#[derive(Clone)] +#[derive(Clone, Copy)] enum ValueState<'a> { None, Bool(bool), @@ -513,6 +535,26 @@ enum ValueOwnedState { Map(Box>), } +macro_rules! impl_value_owned_from { + ($ty:ty, $new:ident) => { + impl From<$ty> for ValueOwned { + fn from(v: $ty) -> Self { + Self::$new(v) + } + } + }; +} + +impl_value_owned_from!(bool, bool); +impl_value_owned_from!(i64, i64); +impl_value_owned_from!(u64, u64); +impl_value_owned_from!(f64, f64); +impl_value_owned_from!(i128, i128); +impl_value_owned_from!(u128, u128); +impl_value_owned_from!(char, char); +impl_value_owned_from!(String, str); +impl_value_owned_from!(&'static str, str); + #[cfg(feature = "serde")] impl serde::Serialize for ValueOwned { fn serialize(&self, serializer: S) -> Result { @@ -614,7 +656,7 @@ impl ValueOwned { } /// A borrowed view of a value. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum ValueView<'a> { /// The absence of a value. diff --git a/diagnostics/task-local/Cargo.toml b/diagnostics/task-local/Cargo.toml index dda98af..443874e 100644 --- a/diagnostics/task-local/Cargo.toml +++ b/diagnostics/task-local/Cargo.toml @@ -37,6 +37,7 @@ pin-project = { workspace = true } [dev-dependencies] log = { workspace = true } +pollster = { workspace = true } [lints] workspace = true diff --git a/diagnostics/task-local/src/lib.rs b/diagnostics/task-local/src/lib.rs index 58385a7..e7285ef 100644 --- a/diagnostics/task-local/src/lib.rs +++ b/diagnostics/task-local/src/lib.rs @@ -22,7 +22,7 @@ //! use logforth_diagnostic_task_local::FutureExt; //! //! let fut = async { log::info!("Hello, world!") }; -//! fut.with_task_local_context([("key".into(), "value".into())]); +//! fut.with_task_local_context([("key", "value")]); //! ``` #![cfg_attr(docsrs, feature(doc_cfg))] @@ -30,22 +30,25 @@ use std::cell::RefCell; use std::pin::Pin; +use std::sync::Arc; use std::task::Context; use std::task::Poll; use logforth_core::Diagnostic; use logforth_core::Error; -use logforth_core::kv::Key; -use logforth_core::kv::Value; +use logforth_core::kv::KeyOwned; +use logforth_core::kv::ValueOwned; use logforth_core::kv::Visitor; +type TaskLocalContext = Arc<[(KeyOwned, ValueOwned)]>; + thread_local! { - static TASK_LOCAL_MAP: RefCell> = const { RefCell::new(Vec::new()) }; + static TASK_LOCAL_MAP: RefCell> = const { RefCell::new(Vec::new()) }; } /// A diagnostic that stores key-value pairs in a task-local context. /// -/// See [module-level documentation](self) for usage examples. +/// See the [crate documentation](self) for usage examples. #[derive(Default, Debug, Clone, Copy)] #[non_exhaustive] pub struct TaskLocalDiagnostic {} @@ -54,10 +57,10 @@ impl Diagnostic for TaskLocalDiagnostic { fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error> { TASK_LOCAL_MAP.with(|map| { let map = map.borrow(); - for (key, value) in map.iter() { - let key = Key::borrowed(key.as_str()); - let value = Value::str(value.as_str()); - visitor.visit(key.view(), value.view())?; + for context in map.iter() { + for (key, value) in context.iter() { + visitor.visit(key.view(), value.view())?; + } } Ok(()) }) @@ -66,20 +69,19 @@ impl Diagnostic for TaskLocalDiagnostic { /// An extension trait for futures to run them with a task-local context. /// -/// See [module-level documentation](self) for usage examples. +/// See the [crate documentation](self) for usage examples. pub trait FutureExt: Future { /// Run a future with a task-local context. - fn with_task_local_context( - self, - kvs: impl IntoIterator, - ) -> impl Future + fn with_task_local_context(self, kvs: I) -> impl Future where Self: Sized, + K: Into, + V: Into, + I: IntoIterator, { - TaskLocalFuture { - future: Some(self), - context: kvs.into_iter().collect(), - } + let future = self; + let context = kvs.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); + TaskLocalFuture { future, context } } } @@ -88,8 +90,8 @@ impl FutureExt for F {} #[pin_project::pin_project] struct TaskLocalFuture { #[pin] - future: Option, - context: Vec<(String, String)>, + future: F, + context: Arc<[(KeyOwned, ValueOwned)]>, } impl Future for TaskLocalFuture { @@ -98,45 +100,47 @@ impl Future for TaskLocalFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - let mut fut = this.future; - if let Some(future) = fut.as_mut().as_pin_mut() { - struct Guard { - n: usize, - } + let future = this.future; - impl Drop for Guard { - fn drop(&mut self) { - TASK_LOCAL_MAP.with(|map| { - let mut map = map.borrow_mut(); - for _ in 0..self.n { - map.pop(); - } - }); - } + struct Guard(()); + + impl Drop for Guard { + fn drop(&mut self) { + TASK_LOCAL_MAP.with(|map| map.borrow_mut().pop()); } + } - TASK_LOCAL_MAP.with(|map| { - let mut map = map.borrow_mut(); - for (key, value) in this.context.iter() { - map.push((key.clone(), value.clone())); - } - }); + TASK_LOCAL_MAP.with(|map| map.borrow_mut().push(this.context.clone())); - let n = this.context.len(); - let guard = Guard { n }; + let guard = Guard(()); - let result = match future.poll(cx) { - Poll::Ready(output) => { - fut.set(None); - Poll::Ready(output) - } - Poll::Pending => Poll::Pending, - }; + let result = future.poll(cx); - drop(guard); - return result; - } + drop(guard); + result + } +} - unreachable!("TaskLocalFuture polled after completion"); +#[cfg(test)] +mod tests { + use logforth_core::kv::KeyView; + use logforth_core::kv::ValueView; + + use super::*; + + #[test] + fn test_task_local_diagnostic() { + let diag = TaskLocalDiagnostic {}; + let fut = async { + let mut n = 0; + diag.visit(&mut |key: KeyView<'_>, value: ValueView<'_>| { + n += 1; + assert_eq!(key.as_str(), format!("k{n}")); + assert_eq!(value.to_str().unwrap(), format!("v{n}")); + Ok(()) + }) + .unwrap(); + }; + pollster::block_on(fut.with_task_local_context([("k1", "v1"), ("k2", "v2")])); } } diff --git a/filters/rustlog/src/lib.rs b/filters/rustlog/src/lib.rs index 728d209..d141e4a 100644 --- a/filters/rustlog/src/lib.rs +++ b/filters/rustlog/src/lib.rs @@ -98,7 +98,7 @@ pub const DEFAULT_FILTER_ENV: &str = "RUST_LOG"; /// Less exclusive levels (like `trace` or `info`) are considered to be more verbose than more /// exclusive levels (like `error` or `warn`). /// -/// Read more from the [crate level documentation](self) about the directive syntax and use cases. +/// Read more from the [crate documentation](self) about the directive syntax and use cases. /// /// [`Record`]: logforth_core::record::Record #[derive(Debug)] diff --git a/layouts/google-cloud-logging/src/lib.rs b/layouts/google-cloud-logging/src/lib.rs index 9a93a50..7353b1e 100644 --- a/layouts/google-cloud-logging/src/lib.rs +++ b/layouts/google-cloud-logging/src/lib.rs @@ -152,7 +152,7 @@ impl Visitor for KvCollector<'_> { } } - let value = match serde_json::to_value(&value) { + let value = match serde_json::to_value(value) { Ok(value) => value, Err(_) => value.to_string().into(), }; diff --git a/layouts/json/src/lib.rs b/layouts/json/src/lib.rs index 6afa8a5..8b87abe 100644 --- a/layouts/json/src/lib.rs +++ b/layouts/json/src/lib.rs @@ -113,7 +113,7 @@ struct KvCollector<'a> { impl Visitor for KvCollector<'_> { fn visit(&mut self, key: KeyView, value: ValueView) -> Result<(), Error> { let key = key.to_string(); - match serde_json::to_value(&value) { + match serde_json::to_value(value) { Ok(value) => self.kvs.insert(key, value), Err(_) => self.kvs.insert(key, value.to_string().into()), };