Skip to content

Commit

Permalink
fill in some more doc examples
Browse files Browse the repository at this point in the history
  • Loading branch information
KodrAus committed Jan 29, 2024
1 parent 9fae53d commit 05119e1
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 35 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -61,6 +61,7 @@ value-bag = { version = "1.4", optional = true, default-features = false }

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_test = "1.0"
sval = { version = "2.1" }
sval_derive = { version = "2.1" }
Expand Down
2 changes: 1 addition & 1 deletion src/kv/key.rs
Expand Up @@ -30,7 +30,7 @@ impl ToKey for str {
}
}

/// A key in a user-defined attribute.
/// A key in a key-value.
// These impls must only be based on the as_str() representation of the key
// If a new field (such as an optional index) is added to the key they must not affect comparison
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down
166 changes: 145 additions & 21 deletions src/kv/mod.rs
Expand Up @@ -19,62 +19,186 @@
//! data processing techniques, without needing to find and parse attributes from
//! unstructured text first.
//!
//! In `log`, user-defined attributes are part of a [`Source`] on the [`LogRecord`].
//! Each attribute is 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
//! 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
//! like strings, booleans, and numbers are supported, as well as arbitrarily complex
//! structures involving nested objects and sequences.
//!
//! ## Adding attributes to log records
//! ## Adding key-values to log records
//!
//! Attributes appear after the message format in the `log!` macros:
//! Key-values appear after the message format in the `log!` macros:
//!
//! ```
//! ..
//! ```
//!
//! ## Working with attributes on log records
//! ## Working with key-values on log records
//!
//! Use the [`LogRecord::source`] method to access user-defined attributes.
//! Individual attributes can be pulled from the source:
//! 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);
//! let a: Value = record.key_values().get(Key::from("a")).unwrap();
//! # Ok(())
//! # }
//! ```
//!
//! This is convenient when an attribute of interest is known in advance.
//! All attributes can also be enumerated using a [`Visitor`]:
//! All key-values can also be enumerated using a [`source::Visitor`]:
//!
//! ```
//! ..
//! # 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<Key<'kvs>, 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);
//! record.key_values().visit(&mut visitor)?;
//!
//! let collected = visitor.0;
//!
//! assert_eq!(
//! vec!["a", "b", "c"],
//! collected
//! .keys()
//! .map(|k| k.as_str())
//! .collect::<Vec<_>>(),
//! );
//! # Ok(())
//! # }
//! ```
//!
//! [`Value`]s in attributes have methods for conversions to common types:
//! [`Value`]s have methods for conversions to common types:
//!
//! ```
//! ..
//! # 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);
//! let a = record.key_values().get(Key::from("a")).unwrap();
//!
//! assert_eq!(1, a.to_i64().unwrap());
//! # Ok(())
//! # }
//! ```
//!
//! Values also have their own [`value::Visitor`] type:
//! Values also have their own [`value::Visitor`] type. Visitors are a lightweight
//! API for working with primitives types:
//!
//! ```
//! ..
//! # 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;
//! Ok(())
//! }
//!
//! fn visit_u64(&mut self, _value: u64) -> Result<(), kv::Error> {
//! self.0 = true;
//! Ok(())
//! }
//!
//! fn visit_i64(&mut self, _value: i64) -> Result<(), kv::Error> {
//! self.0 = true;
//! Ok(())
//! }
//!
//! fn visit_u128(&mut self, _value: u128) -> Result<(), kv::Error> {
//! self.0 = true;
//! Ok(())
//! }
//!
//! fn visit_i128(&mut self, _value: i128) -> Result<(), kv::Error> {
//! self.0 = true;
//! Ok(())
//! }
//!
//! fn visit_f64(&mut self, _value: f64) -> Result<(), kv::Error> {
//! self.0 = true;
//! Ok(())
//! }
//! }
//!
//! // info!("Something of interest"; a = 1);
//! 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(())
//! # }
//! ```
//!
//! Visitors on values are lightweight and suitable for detecting primitive types.
//! To serialize a value, you can also use either `serde` or `sval`:
//! To serialize a value to a format like JSON, you can also use either `serde` or `sval`:
//!
//! ```
//! ..
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # #[cfg(feature = "serde")]
//! # {
//! # use log::kv::Key;
//! # #[derive(serde::Serialize)] 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_serde(&data))];
//! # let record = log::Record::builder().key_values(&source).build();
//!
//! // info!("Something of interest"; a = data);
//! 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.
//! A value can be captured using its `serde::Serialize` implementation and still be serialized
//! through `sval` without losing any structure.
//!
//! 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);
//! let a = record.key_values().get(Key::from("a")).unwrap();
//!
//! assert_eq!("Data { a: 1, b: true, c: \"Some data\" }", format!("{a:?}"));
//! ```

mod error;
Expand Down
70 changes: 58 additions & 12 deletions src/kv/source.rs
@@ -1,34 +1,63 @@
//! Sources for user-defined attributes.
//! Sources for key-values.
//!
//! This module defines the [`Source`] type and supporting APIs for
//! working with collections of attributes.
//! working with collections of key-values.

use crate::kv::{Error, Key, ToKey, ToValue, Value};
use std::fmt;

/// A source of user-defined attributes.
/// A source of key-values.
///
/// 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.
///
/// # Examples
///
/// Enumerating the attributes in a source:
/// Enumerating the key-values in a source:
///
/// ```
/// ..
/// # 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 = &[
/// ("a", 1),
/// ("b", 2),
/// ("c", 3),
/// ];
///
/// // Pass an instance of the `Visitor` to a `Source` to visit it
/// source.visit(&mut Printer)?;
/// # Ok(())
/// # }
/// ```
pub trait Source {
/// Visit attributes.
/// Visit key-values.
///
/// A source doesn't have to guarantee any ordering or uniqueness of attributes.
/// A source doesn't have to guarantee any ordering or uniqueness of key-values.
/// If the given visitor returns an error then the source may early-return with it,
/// even if there are more attributes.
/// even if there are more key-values.
///
/// # Implementation notes
///
/// A source should yield the same attributes to a subsequent visitor unless
/// A source should yield the same key-values to a subsequent visitor unless
/// that visitor itself fails.
fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error>;

Expand All @@ -45,14 +74,14 @@ pub trait Source {
get_default(self, key)
}

/// Count the number of attributes that can be visited.
/// Count the number of key-values that can be visited.
///
/// # Implementation notes
///
/// A source that knows the number of attributes upfront may provide a more
/// A source that knows the number of key-values upfront may provide a more
/// efficient implementation.
///
/// A subsequent call to `visit` should yield the same number of attributes
/// A subsequent call to `visit` should yield the same number of key-values
/// to the visitor, unless that visitor fails part way through.
fn count(&self) -> usize {
count_default(self)
Expand Down Expand Up @@ -165,6 +194,23 @@ where
}
}

impl<const N: usize, S> Source for [S; N]
where
S: Source,
{
fn visit<'kvs>(&'kvs self, visitor: &mut dyn Visitor<'kvs>) -> Result<(), Error> {
Source::visit(self as &[_], visitor)
}

fn get(&self, key: Key) -> Option<Value<'_>> {
Source::get(self as &[_], key)
}

fn count(&self) -> usize {
Source::count(self as &[_])
}
}

impl<S> Source for Option<S>
where
S: Source,
Expand Down
2 changes: 1 addition & 1 deletion src/kv/value.rs
Expand Up @@ -30,7 +30,7 @@ impl<'v> ToValue for Value<'v> {
}
}

/// A value in a user-defined attribute.
/// A value in a key-value.
///
/// Values are an anonymous bag containing some structured datum.
///
Expand Down

0 comments on commit 05119e1

Please sign in to comment.