ecs_logger/extra_fields.rs
1//! Extra fields for the log output
2//!
3//! This module provides a way to add extra fields to the log output.
4//!
5//! ## Example
6//!
7//! ```
8//! use ecs_logger::extra_fields;
9//! use serde::Serialize;
10//!
11//! #[derive(Serialize)]
12//! struct MyExtraFields {
13//! my_field: String,
14//! }
15//!
16//! ecs_logger::init();
17//!
18//! extra_fields::set_extra_fields(MyExtraFields {
19//! my_field: "my_value".to_string(),
20//! }).unwrap();
21//!
22//! log::error!("Hello {}!", "world");
23//! log::info!("Goodbye {}!", "world");
24//!
25//! extra_fields::clear_extra_fields();
26//! ```
27
28use serde_json::{Map, Value};
29use std::sync::RwLock;
30use thiserror::Error;
31
32type JsonMap = Map<String, Value>;
33
34static EXTRA_FIELDS: RwLock<Option<JsonMap>> = RwLock::new(None);
35
36/// Error returned by [`set_extra_fields`].
37#[derive(Error, Debug)]
38pub enum SetExtraFieldsError {
39 /// The data cannot be converted into JSON.
40 ///
41 /// ```
42 /// use std::collections::BTreeMap;
43 /// use ecs_logger::extra_fields::{set_extra_fields, SetExtraFieldsError};
44 ///
45 /// let mut map = BTreeMap::new();
46 /// map.insert(vec![32, 64], "x86");
47 /// assert!(matches!(
48 /// set_extra_fields(map),
49 /// Err(SetExtraFieldsError::InvalidJson(_))
50 /// ));
51 /// ```
52 #[error("the data cannot be converted into JSON")]
53 InvalidJson(#[from] serde_json::Error),
54
55 /// The data cannot be converted into a JSON object.
56 ///
57 /// ```
58 /// use ecs_logger::extra_fields::{set_extra_fields, SetExtraFieldsError};
59 ///
60 /// assert!(matches!(
61 /// set_extra_fields(42),
62 /// Err(SetExtraFieldsError::NotObject)
63 /// ));
64 /// ```
65 #[error("the data cannot be converted into a JSON object")]
66 NotObject,
67}
68
69/// Configure extra fields added to the log record.
70///
71/// This function may be called multiple times, either before or after `ecs_logger::init`.
72/// All extra fields previously set by this function will be cleared.
73///
74/// # Example
75///
76/// ```
77/// use ecs_logger::extra_fields;
78/// use serde::Serialize;
79///
80/// #[derive(Serialize)]
81/// struct MyExtraFields {
82/// my_field: String,
83/// }
84///
85/// extra_fields::set_extra_fields(MyExtraFields {
86/// my_field: "my_value".to_string(),
87/// }).unwrap();
88/// ```
89pub fn set_extra_fields(extra_fields: impl serde::Serialize) -> Result<(), SetExtraFieldsError> {
90 let v = serde_json::to_value(extra_fields)?;
91 let json_map = match v {
92 Value::Object(m) => Some(m),
93 _ => return Err(SetExtraFieldsError::NotObject),
94 };
95
96 {
97 let mut w = EXTRA_FIELDS.write().unwrap();
98 *w = json_map;
99 }
100
101 Ok(())
102}
103
104/// Clear all extra fields previously set by [`set_extra_fields`].
105///
106/// # Example
107///
108/// ```
109/// use ecs_logger::extra_fields;
110/// use serde::Serialize;
111///
112/// #[derive(Serialize)]
113/// struct MyExtraFields {
114/// my_field: String,
115/// }
116///
117/// extra_fields::set_extra_fields(MyExtraFields {
118/// my_field: "my_value".to_string(),
119/// }).unwrap();
120///
121/// extra_fields::clear_extra_fields();
122/// ```
123pub fn clear_extra_fields() {
124 let mut w = EXTRA_FIELDS.write().unwrap();
125 *w = None;
126}
127
128/// Deep merge extra fields into `json_map`
129pub(crate) fn merge_extra_fields(mut json_map: JsonMap) -> JsonMap {
130 let r = EXTRA_FIELDS.read().unwrap();
131 if let Some(extra_fields) = &*r {
132 extend_json_map(&mut json_map, extra_fields);
133 }
134
135 json_map
136}
137
138/// Deep merge `b` into `a`
139fn extend_json_map(a: &mut JsonMap, b: &JsonMap) {
140 for (k, v) in b {
141 match (a.get_mut(k), v) {
142 (Some(Value::Object(a)), Value::Object(b)) => extend_json_map(a, b),
143 (Some(a), b) => {
144 *a = b.clone();
145 }
146 (None, b) => {
147 a.insert(k.clone(), b.clone());
148 }
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use serde_json::json;
157 use std::collections::BTreeMap;
158
159 #[test]
160 fn test_set_extra_fields_ok() {
161 set_extra_fields(json!({
162 "a": 1,
163 "b": {
164 "c": 2,
165 },
166 }))
167 .unwrap();
168
169 let r = EXTRA_FIELDS.read().unwrap();
170 assert!(r.is_some());
171 assert_eq!(
172 serde_json::to_string(r.as_ref().unwrap()).unwrap(),
173 json!({
174 "a": 1,
175 "b": {
176 "c": 2,
177 },
178 })
179 .to_string()
180 );
181 }
182
183 #[test]
184 fn test_set_extra_fields_err() {
185 let mut map = BTreeMap::new();
186 map.insert(vec![32, 64], "x86");
187 assert!(matches!(
188 set_extra_fields(map),
189 Err(SetExtraFieldsError::InvalidJson(_))
190 ));
191
192 assert!(matches!(
193 set_extra_fields(1),
194 Err(SetExtraFieldsError::NotObject)
195 ));
196 }
197
198 #[test]
199 fn test_clear_extra_fields() {
200 set_extra_fields(json!({
201 "a": 1,
202 "b": {
203 "c": 2,
204 },
205 }))
206 .unwrap();
207
208 clear_extra_fields();
209
210 let r = EXTRA_FIELDS.read().unwrap();
211 assert!(r.is_none());
212 }
213
214 #[test]
215 fn test_merge_extra_fields() {
216 set_extra_fields(json!({
217 "b": {
218 "d": 3,
219 },
220 "e": 4,
221 }))
222 .unwrap();
223
224 let mut a = json!({
225 "a": 1,
226 "b": {
227 "c": 2,
228 },
229 });
230 let a_with_extra_fields = merge_extra_fields(a.as_object_mut().unwrap().clone());
231
232 assert_eq!(
233 serde_json::to_string(&Value::Object(a_with_extra_fields)).unwrap(),
234 json!({
235 "a": 1,
236 "b": {
237 "c": 2,
238 "d": 3,
239 },
240 "e": 4,
241 })
242 .to_string()
243 );
244 }
245
246 #[test]
247 fn test_extend_json_map() {
248 let mut a = json!({
249 "a": 1,
250 "b": {
251 "c": 2,
252 },
253 });
254 let b = json!({
255 "b": {
256 "d": 3,
257 },
258 "e": 4,
259 });
260
261 let a = a.as_object_mut().unwrap();
262 let b = b.as_object().unwrap();
263
264 extend_json_map(a, b);
265
266 assert_eq!(
267 serde_json::to_string(a).unwrap(),
268 json!({
269 "a": 1,
270 "b": {
271 "c": 2,
272 "d": 3,
273 },
274 "e": 4,
275 })
276 .to_string()
277 );
278 }
279}