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}