1 | //! Structured logging. |
2 | //! |
3 | //! Add the `kv` feature to your `Cargo.toml` to enable |
4 | //! this module: |
5 | //! |
6 | //! ```toml |
7 | //! [dependencies.log] |
8 | //! features = ["kv"] |
9 | //! ``` |
10 | //! |
11 | //! # Structured logging in `log` |
12 | //! |
13 | //! Structured logging enhances traditional text-based log records with user-defined |
14 | //! attributes. Structured logs can be analyzed using a variety of data processing |
15 | //! techniques, without needing to find and parse attributes from unstructured text first. |
16 | //! |
17 | //! In `log`, user-defined attributes are part of a [`Source`] on the log record. |
18 | //! Each attribute is a key-value; a pair of [`Key`] and [`Value`]. Keys are strings |
19 | //! and values are a datum of any type that can be formatted or serialized. Simple types |
20 | //! like strings, booleans, and numbers are supported, as well as arbitrarily complex |
21 | //! structures involving nested objects and sequences. |
22 | //! |
23 | //! ## Adding key-values to log records |
24 | //! |
25 | //! Key-values appear before the message format in the `log!` macros: |
26 | //! |
27 | //! ``` |
28 | //! # use log::info; |
29 | //! info!(a = 1; "Something of interest" ); |
30 | //! ``` |
31 | //! |
32 | //! Key-values support the same shorthand identifier syntax as `format_args`: |
33 | //! |
34 | //! ``` |
35 | //! # use log::info; |
36 | //! let a = 1; |
37 | //! |
38 | //! info!(a; "Something of interest" ); |
39 | //! ``` |
40 | //! |
41 | //! Values are capturing using the [`ToValue`] trait by default. To capture a value |
42 | //! using a different trait implementation, use a modifier after its key. Here's how |
43 | //! the same example can capture `a` using its `Debug` implementation instead: |
44 | //! |
45 | //! ``` |
46 | //! # use log::info; |
47 | //! info!(a:? = 1; "Something of interest" ); |
48 | //! ``` |
49 | //! |
50 | //! The following capturing modifiers are supported: |
51 | //! |
52 | //! - `:?` will capture the value using `Debug`. |
53 | //! - `:debug` will capture the value using `Debug`. |
54 | //! - `:%` will capture the value using `Display`. |
55 | //! - `:display` will capture the value using `Display`. |
56 | //! - `:err` will capture the value using `std::error::Error` (requires the `kv_std` feature). |
57 | //! - `:sval` will capture the value using `sval::Value` (requires the `kv_sval` feature). |
58 | //! - `:serde` will capture the value using `serde::Serialize` (requires the `kv_serde` feature). |
59 | //! |
60 | //! ## Working with key-values on log records |
61 | //! |
62 | //! Use the [`Record::key_values`](../struct.Record.html#method.key_values) method to access key-values. |
63 | //! |
64 | //! Individual values can be pulled from the source by their key: |
65 | //! |
66 | //! ``` |
67 | //! # fn main() -> Result<(), log::kv::Error> { |
68 | //! use log::kv::{Source, Key, Value}; |
69 | //! # let record = log::Record::builder().key_values(&[("a" , 1)]).build(); |
70 | //! |
71 | //! // info!(a = 1; "Something of interest"); |
72 | //! |
73 | //! let a: Value = record.key_values().get(Key::from("a" )).unwrap(); |
74 | //! assert_eq!(1, a.to_i64().unwrap()); |
75 | //! # Ok(()) |
76 | //! # } |
77 | //! ``` |
78 | //! |
79 | //! All key-values can also be enumerated using a [`VisitSource`]: |
80 | //! |
81 | //! ``` |
82 | //! # fn main() -> Result<(), log::kv::Error> { |
83 | //! use std::collections::BTreeMap; |
84 | //! |
85 | //! use log::kv::{self, Source, Key, Value, VisitSource}; |
86 | //! |
87 | //! struct Collect<'kvs>(BTreeMap<Key<'kvs>, Value<'kvs>>); |
88 | //! |
89 | //! impl<'kvs> VisitSource<'kvs> for Collect<'kvs> { |
90 | //! fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), kv::Error> { |
91 | //! self.0.insert(key, value); |
92 | //! |
93 | //! Ok(()) |
94 | //! } |
95 | //! } |
96 | //! |
97 | //! let mut visitor = Collect(BTreeMap::new()); |
98 | //! |
99 | //! # let record = log::Record::builder().key_values(&[("a" , 1), ("b" , 2), ("c" , 3)]).build(); |
100 | //! // info!(a = 1, b = 2, c = 3; "Something of interest"); |
101 | //! |
102 | //! record.key_values().visit(&mut visitor)?; |
103 | //! |
104 | //! let collected = visitor.0; |
105 | //! |
106 | //! assert_eq!( |
107 | //! vec!["a" , "b" , "c" ], |
108 | //! collected |
109 | //! .keys() |
110 | //! .map(|k| k.as_str()) |
111 | //! .collect::<Vec<_>>(), |
112 | //! ); |
113 | //! # Ok(()) |
114 | //! # } |
115 | //! ``` |
116 | //! |
117 | //! [`Value`]s have methods for conversions to common types: |
118 | //! |
119 | //! ``` |
120 | //! # fn main() -> Result<(), log::kv::Error> { |
121 | //! use log::kv::{Source, Key}; |
122 | //! # let record = log::Record::builder().key_values(&[("a" , 1)]).build(); |
123 | //! |
124 | //! // info!(a = 1; "Something of interest"); |
125 | //! |
126 | //! let a = record.key_values().get(Key::from("a" )).unwrap(); |
127 | //! |
128 | //! assert_eq!(1, a.to_i64().unwrap()); |
129 | //! # Ok(()) |
130 | //! # } |
131 | //! ``` |
132 | //! |
133 | //! Values also have their own [`VisitValue`] type. Value visitors are a lightweight |
134 | //! API for working with primitives types: |
135 | //! |
136 | //! ``` |
137 | //! # fn main() -> Result<(), log::kv::Error> { |
138 | //! use log::kv::{self, Source, Key, VisitValue}; |
139 | //! # let record = log::Record::builder().key_values(&[("a" , 1)]).build(); |
140 | //! |
141 | //! struct IsNumeric(bool); |
142 | //! |
143 | //! impl<'kvs> VisitValue<'kvs> for IsNumeric { |
144 | //! fn visit_any(&mut self, _value: kv::Value) -> Result<(), kv::Error> { |
145 | //! self.0 = false; |
146 | //! Ok(()) |
147 | //! } |
148 | //! |
149 | //! fn visit_u64(&mut self, _value: u64) -> Result<(), kv::Error> { |
150 | //! self.0 = true; |
151 | //! Ok(()) |
152 | //! } |
153 | //! |
154 | //! fn visit_i64(&mut self, _value: i64) -> Result<(), kv::Error> { |
155 | //! self.0 = true; |
156 | //! Ok(()) |
157 | //! } |
158 | //! |
159 | //! fn visit_u128(&mut self, _value: u128) -> Result<(), kv::Error> { |
160 | //! self.0 = true; |
161 | //! Ok(()) |
162 | //! } |
163 | //! |
164 | //! fn visit_i128(&mut self, _value: i128) -> Result<(), kv::Error> { |
165 | //! self.0 = true; |
166 | //! Ok(()) |
167 | //! } |
168 | //! |
169 | //! fn visit_f64(&mut self, _value: f64) -> Result<(), kv::Error> { |
170 | //! self.0 = true; |
171 | //! Ok(()) |
172 | //! } |
173 | //! } |
174 | //! |
175 | //! // info!(a = 1; "Something of interest"); |
176 | //! |
177 | //! let a = record.key_values().get(Key::from("a" )).unwrap(); |
178 | //! |
179 | //! let mut visitor = IsNumeric(false); |
180 | //! |
181 | //! a.visit(&mut visitor)?; |
182 | //! |
183 | //! let is_numeric = visitor.0; |
184 | //! |
185 | //! assert!(is_numeric); |
186 | //! # Ok(()) |
187 | //! # } |
188 | //! ``` |
189 | //! |
190 | //! To serialize a value to a format like JSON, you can also use either `serde` or `sval`: |
191 | //! |
192 | //! ``` |
193 | //! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
194 | //! # #[cfg (feature = "serde" )] |
195 | //! # { |
196 | //! # use log::kv::Key; |
197 | //! #[derive(serde::Serialize)] |
198 | //! struct Data { |
199 | //! a: i32, b: bool, |
200 | //! c: &'static str, |
201 | //! } |
202 | //! |
203 | //! let data = Data { a: 1, b: true, c: "Some data" }; |
204 | //! |
205 | //! # let source = [("a" , log::kv::Value::from_serde(&data))]; |
206 | //! # let record = log::Record::builder().key_values(&source).build(); |
207 | //! // info!(a = data; "Something of interest"); |
208 | //! |
209 | //! let a = record.key_values().get(Key::from("a" )).unwrap(); |
210 | //! |
211 | //! assert_eq!("{ \"a \":1, \"b \":true, \"c \": \"Some data \"}" , serde_json::to_string(&a)?); |
212 | //! # } |
213 | //! # Ok(()) |
214 | //! # } |
215 | //! ``` |
216 | //! |
217 | //! The choice of serialization framework depends on the needs of the consumer. |
218 | //! If you're in a no-std environment, you can use `sval`. In other cases, you can use `serde`. |
219 | //! Log producers and log consumers don't need to agree on the serialization framework. |
220 | //! A value can be captured using its `serde::Serialize` implementation and still be serialized |
221 | //! through `sval` without losing any structure or data. |
222 | //! |
223 | //! Values can also always be formatted using the standard `Debug` and `Display` |
224 | //! traits: |
225 | //! |
226 | //! ``` |
227 | //! # use log::kv::Key; |
228 | //! # #[derive(Debug)] |
229 | //! struct Data { |
230 | //! a: i32, |
231 | //! b: bool, |
232 | //! c: &'static str, |
233 | //! } |
234 | //! |
235 | //! let data = Data { a: 1, b: true, c: "Some data" }; |
236 | //! |
237 | //! # let source = [("a" , log::kv::Value::from_debug(&data))]; |
238 | //! # let record = log::Record::builder().key_values(&source).build(); |
239 | //! // info!(a = data; "Something of interest"); |
240 | //! |
241 | //! let a = record.key_values().get(Key::from("a" )).unwrap(); |
242 | //! |
243 | //! assert_eq!("Data { a: 1, b: true, c: \"Some data \" }" , format!("{a:?}" )); |
244 | //! ``` |
245 | |
246 | mod error; |
247 | mod key; |
248 | |
249 | #[cfg (not(feature = "kv_unstable" ))] |
250 | mod source; |
251 | #[cfg (not(feature = "kv_unstable" ))] |
252 | mod value; |
253 | |
254 | pub use self::error::Error; |
255 | pub use self::key::{Key, ToKey}; |
256 | pub use self::source::{Source, VisitSource}; |
257 | pub use self::value::{ToValue, Value, VisitValue}; |
258 | |
259 | #[cfg (feature = "kv_unstable" )] |
260 | pub mod source; |
261 | #[cfg (feature = "kv_unstable" )] |
262 | pub mod value; |
263 | |
264 | #[cfg (feature = "kv_unstable" )] |
265 | pub use self::source::Visitor; |
266 | |