From 009637717e5b824eb7c1e8d50558b49efd0c6ff0 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 30 Jan 2024 08:02:41 +1000 Subject: [PATCH] some more kv docs --- src/kv/mod.rs | 81 ++++++++++++++++++---------------- src/kv/source.rs | 14 +++--- src/kv/value.rs | 110 +++++++++++++++++++++++++++++++++++------------ src/lib.rs | 2 +- 4 files changed, 135 insertions(+), 72 deletions(-) diff --git a/src/kv/mod.rs b/src/kv/mod.rs index 650fc6da2..7bff51fb2 100644 --- a/src/kv/mod.rs +++ b/src/kv/mod.rs @@ -20,31 +20,33 @@ //! unstructured text first. //! //! In `log`, user-defined attributes are part of a [`Source`] on the log record. -//! Each attribute is a key-value; a pair of [`Key`] and [`Value`]. Keys are strings -//! and values are a datum of any type that can be formatted or serialized. Simple types +//! Each attribute is a key-value; a pair of [`Key`] and [`Value`]. Keys are strings +//! and values are a datum of any type that can be formatted or serialized. Simple types //! like strings, booleans, and numbers are supported, as well as arbitrarily complex //! structures involving nested objects and sequences. //! //! ## Adding key-values to log records //! -//! Key-values appear after the message format in the `log!` macros: +//! Key-values appear before the message format in the `log!` macros: //! //! ``` -//! .. +//! # use log::info; +//! info!(a = 1; "Something of interest"); //! ``` //! //! ## Working with key-values on log records //! //! Use the [`LogRecord::key_values`] method to access key-values. -//! +//! //! Individual values can be pulled from the source by their key: //! //! ``` //! # fn main() -> Result<(), log::kv::Error> { //! use log::kv::{Source, Key, Value}; //! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); -//! -//! // info!("Something of interest"; a = 1); +//! +//! // info!(a = 1; "Something of interest"); +//! //! let a: Value = record.key_values().get(Key::from("a")).unwrap(); //! # Ok(()) //! # } @@ -56,28 +58,29 @@ //! # fn main() -> Result<(), log::kv::Error> { //! # let record = log::Record::builder().key_values(&[("a", 1), ("b", 2), ("c", 3)]).build(); //! use std::collections::BTreeMap; -//! +//! //! use log::kv::{self, Source, Key, Value, source::Visitor}; -//! +//! //! struct Collect<'kvs>(BTreeMap, Value<'kvs>>); -//! +//! //! impl<'kvs> Visitor<'kvs> for Collect<'kvs> { //! fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { //! self.0.insert(key, value); -//! +//! //! Ok(()) //! } //! } -//! +//! //! let mut visitor = Collect(BTreeMap::new()); -//! -//! // info!("Something of interest"; a = 1, b = 2, c = 3); +//! +//! // info!(a = 1, b = 2, c = 3; "Something of interest"); +//! //! record.key_values().visit(&mut visitor)?; -//! +//! //! let collected = visitor.0; -//! +//! //! assert_eq!( -//! vec!["a", "b", "c"], +//! vec!["a", "b", "c"], //! collected //! .keys() //! .map(|k| k.as_str()) @@ -93,10 +96,11 @@ //! # fn main() -> Result<(), log::kv::Error> { //! use log::kv::{Source, Key}; //! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); -//! -//! // info!("Something of interest"; a = 1); +//! +//! // info!(a = 1; "Something of interest"); +//! //! let a = record.key_values().get(Key::from("a")).unwrap(); -//! +//! //! assert_eq!(1, a.to_i64().unwrap()); //! # Ok(()) //! # } @@ -109,9 +113,9 @@ //! # fn main() -> Result<(), log::kv::Error> { //! use log::kv::{self, Source, Key, value::Visitor}; //! # let record = log::Record::builder().key_values(&[("a", 1)]).build(); -//! +//! //! struct IsNumeric(bool); -//! +//! //! impl<'kvs> Visitor<'kvs> for IsNumeric { //! fn visit_any(&mut self, _value: kv::Value) -> Result<(), kv::Error> { //! self.0 = false; @@ -143,23 +147,24 @@ //! Ok(()) //! } //! } -//! -//! // info!("Something of interest"; a = 1); +//! +//! // info!(a = 1; "Something of interest"); +//! //! let a = record.key_values().get(Key::from("a")).unwrap(); -//! +//! //! let mut visitor = IsNumeric(false); -//! +//! //! a.visit(&mut visitor)?; -//! +//! //! let is_numeric = visitor.0; -//! +//! //! assert!(is_numeric); //! # Ok(()) //! # } //! ``` //! //! To serialize a value to a format like JSON, you can also use either `serde` or `sval`: -//! +//! //! ``` //! # fn main() -> Result<(), Box> { //! # #[cfg(feature = "serde")] @@ -169,16 +174,17 @@ //! let data = Data { a: 1, b: true, c: "Some data" }; //! # let source = [("a", log::kv::Value::from_serde(&data))]; //! # let record = log::Record::builder().key_values(&source).build(); -//! -//! // info!("Something of interest"; a = data); +//! +//! // info!(a = data; "Something of interest"); +//! //! let a = record.key_values().get(Key::from("a")).unwrap(); -//! +//! //! assert_eq!("{\"a\":1,\"b\":true,\"c\":\"Some data\"}", serde_json::to_string(&a)?); //! # } //! # Ok(()) //! # } //! ``` -//! +//! //! The choice of serialization framework depends on the needs of the consumer. //! If you're in a no-std environment, you can use `sval`. In other cases, you can use `serde`. //! Log producers and log consumers don't need to agree on the serialization framework. @@ -187,17 +193,18 @@ //! //! Values can also always be formatted using the standard `Debug` and `Display` //! traits: -//! +//! //! ``` //! # use log::kv::Key; //! # #[derive(Debug)] struct Data { a: i32, b: bool, c: &'static str } //! let data = Data { a: 1, b: true, c: "Some data" }; //! # let source = [("a", log::kv::Value::from_debug(&data))]; //! # let record = log::Record::builder().key_values(&source).build(); -//! -//! // info!("Something of interest"; a = data); +//! +//! // info!(a = data; "Something of interest"); +//! //! let a = record.key_values().get(Key::from("a")).unwrap(); -//! +//! //! assert_eq!("Data { a: 1, b: true, c: \"Some data\" }", format!("{a:?}")); //! ``` diff --git a/src/kv/source.rs b/src/kv/source.rs index 69544162d..87d955417 100644 --- a/src/kv/source.rs +++ b/src/kv/source.rs @@ -1,5 +1,5 @@ //! Sources for key-values. -//! +//! //! This module defines the [`Source`] type and supporting APIs for //! working with collections of key-values. @@ -11,7 +11,7 @@ use std::fmt; /// The source may be a single pair, a set of pairs, or a filter over a set of pairs. /// Use the [`Visitor`](trait.Visitor.html) trait to inspect the structured data /// in a source. -/// +/// /// A source is like an iterator over its key-values, except with a push-based API /// instead of a pull-based one. /// @@ -22,19 +22,19 @@ use std::fmt; /// ``` /// # fn main() -> Result<(), log::kv::Error> { /// use log::kv::{self, Source, Key, Value, source::Visitor}; -/// +/// /// // A `Visitor` that prints all key-values /// // Visitors are fed the key-value pairs of each key-values /// struct Printer; -/// +/// /// impl<'kvs> Visitor<'kvs> for Printer { /// fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { /// println!("{key}: {value}"); -/// +/// /// Ok(()) /// } /// } -/// +/// /// // A source with 3 key-values /// // Common collection types implement the `Source` trait /// let source = &[ @@ -42,7 +42,7 @@ use std::fmt; /// ("b", 2), /// ("c", 3), /// ]; -/// +/// /// // Pass an instance of the `Visitor` to a `Source` to visit it /// source.visit(&mut Printer)?; /// # Ok(()) diff --git a/src/kv/value.rs b/src/kv/value.rs index 1abc01bde..c8cdf6dbb 100644 --- a/src/kv/value.rs +++ b/src/kv/value.rs @@ -1,5 +1,5 @@ //! Structured values. -//! +//! //! This module defines the [`Value`] type and supporting APIs for //! capturing and serializing them. @@ -45,7 +45,6 @@ impl<'v> ToValue for Value<'v> { /// ## Using the `Value::from_*` methods /// /// `Value` offers a few constructor methods that capture values of different kinds. -/// These methods don't require `T: 'static`, but can't support downcasting. /// /// ``` /// use log::kv::Value; @@ -79,25 +78,43 @@ impl<'v> ToValue for Value<'v> { /// assert_eq!(Some(42), value.to_i64()); /// ``` /// +/// # Data model +/// +/// Values can hold one of a number of types: +/// +/// - **Null:** The absence of any other meaningful value. Note that +/// `Some(Value::null())` is not the same as `None`. The former is +/// `null` while the latter is `undefined`. This is important to be +/// able to tell the difference between a key-value that was logged, +/// but its value was empty (`Some(Value::null())`) and a key-value +/// that was never logged at all (`None`). +/// - **Strings:** `str`, `char`. +/// - **Booleans:** `bool`. +/// - **Integers:** `u8`-`u128`, `i8`-`i128`, `NonZero*`. +/// - **Floating point numbers:** `f32`-`f64`. +/// - **Errors:** `dyn (Error + 'static)`. +/// - **`serde`:** Any type in `serde`'s data model. +/// - **`sval`:** Any type in `sval`'s data model. +/// /// # Serialization /// -/// `Value` provides a number of ways to be serialized. +/// Values provide a number of ways to be serialized. /// /// For basic types the [`Value::visit`] method can be used to extract the /// underlying typed value. However this is limited in the amount of types -/// supported (see the [`Visit`] trait methods). +/// supported (see the [`Visitor`] trait methods). /// /// For more complex types one of the following traits can be used: -/// * [`sval::Value`], requires the `kv_unstable_sval` feature. -/// * [`serde::Serialize`], requires the `kv_unstable_serde` feature. -/// -/// You don't need a [`Visit`] to serialize values. -/// +/// * `sval::Value`, requires the `kv_unstable_sval` feature. +/// * `serde::Serialize`, requires the `kv_unstable_serde` feature. +/// +/// You don't need a visitor to serialize values through `serde` or `sval`. +/// /// A value can always be serialized using any supported framework, regardless /// of how it was captured. If, for example, a value was captured using its -/// `Display` implementation, it will serialize as a string. If it was captured -/// as a struct using its `serde::Serialize`, it will also serialize as a struct -/// through `sval`, or be formatted using a `Debug`-compatible representation. +/// `Display` implementation, it will serialize through `serde` as a string. If it was +/// captured as a struct using `serde`, it will also serialize as a struct +/// through `sval`, or can be formatted using a `Debug`-compatible representation. pub struct Value<'v> { inner: inner::Inner<'v>, } @@ -175,6 +192,13 @@ impl<'v> Value<'v> { } } + /// Get a `null` value. + pub fn null() -> Self { + Value { + inner: inner::Inner::empty(), + } + } + /// Get a value from an internal primitive. fn from_inner(value: T) -> Self where @@ -458,15 +482,22 @@ mod std_support { } } -/// A visitor for a `Value`. +/// A visitor for a [`Value`]. /// -/// Also see [`Value`'s documentation on seralization]. +/// Also see [`Value`'s documentation on seralization]. Visitors are a simple alternative +/// to a more fully-featured serialization framework like `serde` or `sval`. A visitor +/// can differentiate primitive types through methods like [`Visitor::visit_bool`] and +/// [`Visitor::visit_str`], but more complex types like maps and sequences +/// will fallthrough to [`Visitor::visit_any`]. +/// +/// If you're trying to serialize a value to a format like JSON, you can use either `serde` +/// or `sval` directly with the value. You don't need a visitor. /// /// [`Value`'s documentation on seralization]: Value#serialization pub trait Visitor<'v> { /// Visit a `Value`. /// - /// This is the only required method on `Visit` and acts as a fallback for any + /// This is the only required method on `Visitor` and acts as a fallback for any /// more specific methods that aren't overridden. /// The `Value` may be formatted using its `fmt::Debug` or `fmt::Display` implementation, /// or serialized using its `sval::Value` or `serde::Serialize` implementation. @@ -622,39 +653,57 @@ pub(in crate::kv) mod inner { } fn visit_u64(&mut self, value: u64) -> Result<(), Error> { - self.0.visit_u64(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_u64(value) + .map_err(crate::kv::Error::into_value) } fn visit_i64(&mut self, value: i64) -> Result<(), Error> { - self.0.visit_i64(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_i64(value) + .map_err(crate::kv::Error::into_value) } fn visit_u128(&mut self, value: u128) -> Result<(), Error> { - self.0.visit_u128(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_u128(value) + .map_err(crate::kv::Error::into_value) } fn visit_i128(&mut self, value: i128) -> Result<(), Error> { - self.0.visit_i128(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_i128(value) + .map_err(crate::kv::Error::into_value) } fn visit_f64(&mut self, value: f64) -> Result<(), Error> { - self.0.visit_f64(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_f64(value) + .map_err(crate::kv::Error::into_value) } fn visit_bool(&mut self, value: bool) -> Result<(), Error> { - self.0.visit_bool(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_bool(value) + .map_err(crate::kv::Error::into_value) } fn visit_str(&mut self, value: &str) -> Result<(), Error> { - self.0.visit_str(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_str(value) + .map_err(crate::kv::Error::into_value) } fn visit_borrowed_str(&mut self, value: &'v str) -> Result<(), Error> { - self.0.visit_borrowed_str(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_borrowed_str(value) + .map_err(crate::kv::Error::into_value) } fn visit_char(&mut self, value: char) -> Result<(), Error> { - self.0.visit_char(value).map_err(crate::kv::Error::into_value) + self.0 + .visit_char(value) + .map_err(crate::kv::Error::into_value) } #[cfg(feature = "kv_unstable_std")] @@ -662,7 +711,9 @@ pub(in crate::kv) mod inner { &mut self, err: &(dyn std::error::Error + 'static), ) -> Result<(), Error> { - self.0.visit_error(err).map_err(crate::kv::Error::into_value) + self.0 + .visit_error(err) + .map_err(crate::kv::Error::into_value) } #[cfg(feature = "kv_unstable_std")] @@ -670,7 +721,9 @@ pub(in crate::kv) mod inner { &mut self, err: &'v (dyn std::error::Error + 'static), ) -> Result<(), Error> { - self.0.visit_borrowed_error(err).map_err(crate::kv::Error::into_value) + self.0 + .visit_borrowed_error(err) + .map_err(crate::kv::Error::into_value) } } @@ -826,6 +879,10 @@ pub(in crate::kv) mod inner { todo!() } + pub fn empty() -> Self { + todo!() + } + pub fn to_bool(&self) -> Option { todo!() } @@ -873,7 +930,6 @@ pub(in crate::kv) mod inner { F64(f64), I64(i64), U64(u64), - } #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs index 6a8e57762..86f3f8ec2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ //! # #[cfg(not(feature = "kv_unstable_serde"))] //! # fn main() {} //! ``` -//! +//! //! See the [`kv`] module documentation for more details. //! //! # Available logging implementations