spdlog/kv.rs
1//! Structured logging.
2//!
3//! Structured logging enhances traditional text-based log records with
4//! user-defined attributes. Structured logs can be analyzed using a variety of
5//! data processing techniques, without needing to find and parse attributes
6//! from unstructured text first.
7//!
8//! [`Key`]s are strings and [`Value`]s are a datum of any type that can be
9//! formatted or serialized. Simple types like strings, booleans, and numbers
10//! are supported, as well as arbitrarily complex structures involving nested
11//! objects and sequences.
12//!
13//! [`Value`] uses [_value-bag_ crate] as the backend, which is an alias of
14//! [`value_bag::ValueBag`].
15//!
16//! KVs will be passed into a [`Record`] to be processed by [`Formatter`]s via
17//! [`Record::key_values`] method.
18//!
19//! ## Examples
20//!
21//! #### Basic syntax
22//!
23//! In logging macros, an optional named parameter `kv` (like `logger`) is used
24//! to add key-values to a log.
25//!
26//! ```
27//! # use spdlog::prelude::*;
28//! info!("program started", kv: { pid = std::process::id() });
29//!
30//! # let telemetry = spdlog::default_logger();
31//! trace!(logger: telemetry, "user logged in", kv: { username = "John" });
32//!
33//! let ip = "1.1.1.1";
34//! trace!("DNS setup", kv: { ip });
35//! // ^^ Shorthand syntax, equivalent to `ip = ip`
36//! ```
37//!
38//! #### Modifier
39//!
40//! A value is stored directly with its type by default (after erasure, of
41//! course), using _modifier_ if you want it to be stored in another format.
42//!
43//! | Modifier | Description |
44//! |----------|---------------------------------------------------------------------------------------|
45//! | | No modifier, capture the value directly |
46//! | `:` | Capture the value using [`Display`] trait |
47//! | `:?` | Capture the value using [`Debug`] trait |
48//! | `:sval` | Capture the value using [`sval::Value`] trait, crate feature `sval` is required |
49//! | `:serde` | Capture the value using [`serde::Serialize`] trait, crate feature `serde` is required |
50//!
51//! ```
52//! # use spdlog::prelude::*;
53//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
54//! # struct Url;
55//! # impl Url { fn parse(_: &str) -> Result<Self, Box<dyn std::error::Error>> { Ok(Self) } }
56//! # impl std::fmt::Display for Url {
57//! # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58//! # write!(f, "")
59//! # }
60//! # }
61//! let url = Url::parse("https://example.com")?;
62//! trace!("user browsed website", kv: { url: });
63//! // ^ Capture the value using `Display` trait
64//! // ^^^^ Shorthand syntax, equivalent to `url: = url`
65//!
66//! let orders = vec!["coffee", "pizza", "soup"];
67//! info!("order received", kv: { orders:? });
68//! // ^^ Capture the value using `Debug` trait
69//! // ^^^^^^^^ Shorthand syntax, equivalent to `orders:? = orders`
70//!
71//! #[derive(sval_derive::Value)]
72//! struct Point { x: f32, y: f32 }
73//!
74//! let pos = Point { x: 11.4, y: 5.14 };
75//! # #[cfg(feature = "sval")]
76//! trace!("user clicked", kv: { pos:sval });
77//! // ^^^^^ Capture the value using `sval::Value` trait
78//! // ^^^^^^^^ Shorthand syntax, equivalent to `pos:sval = pos`
79//! # Ok(()) }
80//! ```
81//! [_value-bag_ crate]: https://crates.io/crates/value-bag
82//! [`Record`]: crate::Record
83//! [`Formatter`]: crate::formatter::Formatter
84//! [`Display`]: std::fmt::Display
85//! [`Record::key_values`]: crate::Record::key_values
86//!
87//! [`sval::Value`]: https://docs.rs/sval/latest/sval/trait.Value.html
88// TODO: This above link annotation is unnecessary, but Rustdoc has bug:
89// https://github.com/rust-lang/cargo/issues/3475
90// Remove it when the bug is fixed.
91use std::{borrow::Cow, fmt, slice};
92
93use value_bag::{OwnedValueBag, ValueBag};
94
95use crate::utils::RefStr;
96
97/// Represents a key in a key-value pair.
98#[derive(Debug, Clone)]
99pub struct Key<'a>(RefStr<'a>);
100
101impl Key<'_> {
102 /// Gets the key string.
103 pub fn as_str(&self) -> &str {
104 self.0.get()
105 }
106}
107
108impl<'a> Key<'a> {
109 #[doc(hidden)]
110 pub fn __from_static_str(key: &'static str) -> Self {
111 Key(RefStr::new_static(key))
112 }
113
114 pub(crate) fn from_str(key: &'a str) -> Self {
115 Key(RefStr::new_ref(key))
116 }
117
118 pub(crate) fn to_owned(&self) -> KeyOwned {
119 KeyOwned(self.0.to_cow_static())
120 }
121
122 #[cfg(test)]
123 pub(crate) fn inner(&self) -> RefStr<'a> {
124 self.0
125 }
126}
127
128impl PartialEq for Key<'_> {
129 fn eq(&self, other: &Self) -> bool {
130 self.as_str() == other.as_str()
131 }
132}
133
134#[derive(Debug, Clone)]
135pub(crate) struct KeyOwned(Cow<'static, str>);
136
137impl KeyOwned {
138 pub(crate) fn as_ref(&self) -> Key<'_> {
139 Key(RefStr::new_ref(&self.0))
140 }
141}
142
143/// Represents a value in a key-value pair.
144pub type Value<'a> = ValueBag<'a>;
145pub(crate) type ValueOwned = OwnedValueBag;
146
147enum KeyValuesInner<'a> {
148 Borrowed(&'a [Pair<'a>]),
149 Owned(&'a [(KeyOwned, ValueOwned)]),
150}
151enum KeyValuesIterInner<'a> {
152 Borrowed(slice::Iter<'a, Pair<'a>>),
153 Owned(slice::Iter<'a, (KeyOwned, ValueOwned)>),
154}
155
156/// Represents a collection of key-value pairs.
157///
158/// ## Examples
159///
160/// ```
161/// use std::fmt::Write;
162/// use spdlog::{
163/// formatter::{Formatter, FormatterContext},
164/// Record, StringBuf,
165/// };
166///
167/// #[derive(Clone)]
168/// struct MyFormatter;
169///
170/// impl Formatter for MyFormatter {
171/// fn format(
172/// &self,
173/// record: &Record,
174/// dest: &mut StringBuf,
175/// ctx: &mut FormatterContext,
176/// ) -> spdlog::Result<()> {
177/// dest.write_str(record.payload())
178/// .map_err(spdlog::Error::FormatRecord)?;
179/// for (key, value) in record.key_values() {
180/// write!(dest, " {}={}", key.as_str(), value).map_err(spdlog::Error::FormatRecord)?;
181/// }
182/// Ok(())
183/// }
184/// }
185/// ```
186pub struct KeyValues<'a>(KeyValuesInner<'a>);
187
188impl<'a> KeyValues<'a> {
189 /// Gets the number of key-value pairs.
190 pub fn len(&self) -> usize {
191 match self.0 {
192 KeyValuesInner::Borrowed(p) => p.len(),
193 KeyValuesInner::Owned(p) => p.len(),
194 }
195 }
196
197 /// Checks if there are no key-value pairs.
198 pub fn is_empty(&self) -> bool {
199 match self.0 {
200 KeyValuesInner::Borrowed(p) => p.is_empty(),
201 KeyValuesInner::Owned(p) => p.is_empty(),
202 }
203 }
204
205 /// Gets the value of the specified key.
206 pub fn get(&self, key: Key) -> Option<Value<'a>> {
207 match self.0 {
208 KeyValuesInner::Borrowed(p) => {
209 p.iter()
210 .find_map(|(k, v)| if k == &key { Some(v.clone()) } else { None })
211 }
212 KeyValuesInner::Owned(p) => p.iter().find_map(|(k, v)| {
213 if k.as_ref() == key {
214 Some(v.by_ref())
215 } else {
216 None
217 }
218 }),
219 }
220 }
221
222 /// Gets an iterator over the key-value pairs.
223 pub fn iter(&self) -> KeyValuesIter<'a> {
224 match &self.0 {
225 KeyValuesInner::Borrowed(p) => KeyValuesIter(KeyValuesIterInner::Borrowed(p.iter())),
226 KeyValuesInner::Owned(p) => KeyValuesIter(KeyValuesIterInner::Owned(p.iter())),
227 }
228 }
229
230 pub(crate) fn with_borrowed(pairs: &'a [Pair<'a>]) -> Self {
231 Self(KeyValuesInner::Borrowed(pairs))
232 }
233
234 pub(crate) fn with_owned(pairs: &'a [(KeyOwned, ValueOwned)]) -> Self {
235 Self(KeyValuesInner::Owned(pairs))
236 }
237
238 pub(crate) fn write_to(&self, dest: &mut impl fmt::Write, brackets: bool) -> fmt::Result {
239 let mut iter = self.iter();
240 let first = iter.next();
241 if let Some((key, value)) = first {
242 if brackets {
243 dest.write_str(" { ")?;
244 }
245
246 // Reduce branch prediction misses for performance
247 // So we manually process the first KV pair
248 dest.write_str(key.as_str())?;
249 dest.write_str("=")?;
250 write!(dest, "{value}")?;
251
252 for (key, value) in iter {
253 dest.write_str(" ")?;
254 dest.write_str(key.as_str())?;
255 dest.write_str("=")?;
256 write!(dest, "{value}")?;
257 }
258
259 if brackets {
260 dest.write_str(" }")?;
261 }
262 }
263 Ok(())
264 }
265}
266
267impl<'a> IntoIterator for KeyValues<'a> {
268 type Item = Pair<'a>;
269 type IntoIter = KeyValuesIter<'a>;
270
271 fn into_iter(self) -> Self::IntoIter {
272 self.iter()
273 }
274}
275
276/// Represents an iterator over key-value pairs.
277pub struct KeyValuesIter<'a>(KeyValuesIterInner<'a>);
278
279impl<'a> Iterator for KeyValuesIter<'a> {
280 type Item = Pair<'a>;
281
282 fn next(&mut self) -> Option<Self::Item> {
283 match &mut self.0 {
284 // The 2 clones should be cheap
285 KeyValuesIterInner::Borrowed(iter) => iter.next().map(|(k, v)| (k.clone(), v.clone())),
286 KeyValuesIterInner::Owned(iter) => iter.next().map(|(k, v)| (k.as_ref(), v.by_ref())),
287 }
288 }
289
290 fn size_hint(&self) -> (usize, Option<usize>) {
291 match &self.0 {
292 KeyValuesIterInner::Borrowed(iter) => iter.size_hint(),
293 KeyValuesIterInner::Owned(iter) => iter.size_hint(),
294 }
295 }
296}
297
298pub(crate) type Pair<'a> = (Key<'a>, Value<'a>);
299
300#[cfg(feature = "log")]
301pub(crate) struct LogCrateConverter<'a>(Vec<(log::kv::Key<'a>, ValueOwned)>);
302
303#[cfg(feature = "log")]
304impl<'a> LogCrateConverter<'a> {
305 pub(crate) fn new(capacity: usize) -> Self {
306 Self(Vec::with_capacity(capacity))
307 }
308
309 pub(crate) fn finalize(self) -> Vec<(log::kv::Key<'a>, ValueOwned)> {
310 self.0
311 }
312}
313
314#[cfg(feature = "log")]
315impl<'a> log::kv::VisitSource<'a> for LogCrateConverter<'a> {
316 fn visit_pair(
317 &mut self,
318 key: log::kv::Key<'a>,
319 value: log::kv::Value<'a>,
320 ) -> Result<(), log::kv::Error> {
321 struct Visitor(Option<ValueOwned>);
322
323 macro_rules! visit_fn {
324 ( $($fn:ident: $ty:ty => $from:ident),+$(,)? ) => {
325 $(fn $fn(&mut self, value: $ty) -> Result<(), log::kv::Error> {
326 self.0 = Some(Value::$from(value).to_owned());
327 Ok(())
328 })+
329 };
330 }
331
332 impl log::kv::VisitValue<'_> for Visitor {
333 fn visit_any(&mut self, value: log::kv::Value) -> Result<(), log::kv::Error> {
334 // Since we have no way to extract the underlying `&dyn Display`, we have to
335 // `to_owned()` here
336 self.0 = Some(Value::from_display(&value).to_owned());
337 Ok(())
338 }
339
340 fn visit_null(&mut self) -> Result<(), log::kv::Error> {
341 self.0 = Some(Value::empty().to_owned());
342 Ok(())
343 }
344
345 visit_fn! {
346 visit_u64: u64 => from_u64,
347 visit_i64: i64 => from_i64,
348 visit_u128: u128 => from_u128,
349 visit_i128: i128 => from_i128,
350 visit_f64: f64 => from_f64,
351 visit_bool: bool => from_bool,
352 visit_str: &str => from_str,
353 visit_borrowed_str: &str => from_str,
354 visit_char: char => from_char,
355 }
356 }
357
358 let mut visitor = Visitor(None);
359 value.visit(&mut visitor)?;
360 self.0.push((key, visitor.0.unwrap()));
361 Ok(())
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn key_partial_eq() {
371 assert_eq!(Key::from_str("a"), Key::__from_static_str("a"));
372 assert_ne!(Key::from_str("a"), Key::__from_static_str("b"));
373 }
374}