Expand description
Structured logging.
Add the kv
feature to your Cargo.toml
to enable
this module:
[dependencies.log]
features = ["kv"]
§Structured logging in log
Structured logging enhances traditional text-based log records with user-defined attributes. Structured logs can be analyzed using a variety of 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 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 key-values to log records
Key-values appear before the message format in the log!
macros:
info!(a = 1; "Something of interest");
Key-values support the same shorthand identifer syntax as format_args
:
let a = 1;
info!(a; "Something of interest");
Values are capturing using the ToValue
trait by default. To capture a value
using a different trait implementation, use a modifier after its key. Here’s how
the same example can capture a
using its Debug
implementation instead:
info!(a:? = 1; "Something of interest");
The following capturing modifiers are supported:
:?
will capture the value usingDebug
.:debug
will capture the value usingDebug
.:%
will capture the value usingDisplay
.:display
will capture the value usingDisplay
.:err
will capture the value usingstd::error::Error
(requires thekv_std
feature).:sval
will capture the value usingsval::Value
(requires thekv_sval
feature).:serde
will capture the value usingserde::Serialize
(requires thekv_serde
feature).
§Working with key-values on log records
Use the Record::key_values
method to access key-values.
Individual values can be pulled from the source by their key:
use log::kv::{Source, Key, Value};
// info!(a = 1; "Something of interest");
let a: Value = record.key_values().get(Key::from("a")).unwrap();
assert_eq!(1, a.to_i64().unwrap());
All key-values can also be enumerated using a VisitSource
:
use std::collections::BTreeMap;
use log::kv::{self, Source, Key, Value, VisitSource};
struct Collect<'kvs>(BTreeMap<Key<'kvs>, Value<'kvs>>);
impl<'kvs> VisitSource<'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!(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"],
collected
.keys()
.map(|k| k.as_str())
.collect::<Vec<_>>(),
);
Value
s have methods for conversions to common types:
use log::kv::{Source, Key};
// info!(a = 1; "Something of interest");
let a = record.key_values().get(Key::from("a")).unwrap();
assert_eq!(1, a.to_i64().unwrap());
Values also have their own VisitValue
type. Value visitors are a lightweight
API for working with primitives types:
use log::kv::{self, Source, Key, VisitValue};
struct IsNumeric(bool);
impl<'kvs> VisitValue<'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!(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);
To serialize a value to a format like JSON, you can also use either serde
or sval
:
#[derive(serde::Serialize)]
struct Data {
a: i32, b: bool,
c: &'static str,
}
let data = Data { a: 1, b: true, c: "Some 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)?);
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 or data.
Values can also always be formatted using the standard Debug
and Display
traits:
struct Data {
a: i32,
b: bool,
c: &'static str,
}
let data = Data { a: 1, b: true, c: "Some 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:?}"));
Structs§
- An error encountered while working with structured data.
- A key in a key-value.
- A value in a key-value.