Skip to main content

rivet_logger/processors/
closure_context.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use crate::logger::{BoxError, LogRecord, LogValue, Processor};
5
6#[derive(Default)]
7pub struct ClosureContext;
8
9impl Processor for ClosureContext {
10    fn process(&self, mut record: LogRecord) -> Result<LogRecord, BoxError> {
11        let deferred = if record.context.len() == 1 {
12            record.context.values().find_map(|value| {
13                if let LogValue::Deferred(value) = value {
14                    Some(Arc::clone(value))
15                } else {
16                    None
17                }
18            })
19        } else {
20            None
21        };
22
23        if let Some(deferred) = deferred {
24            match deferred.resolve() {
25                Ok(LogValue::Map(context)) => {
26                    record.context = context;
27                }
28                Ok(other) => {
29                    let mut context = BTreeMap::new();
30                    context.insert("value".to_string(), other);
31                    record.context = context;
32                }
33                Err(err) => {
34                    let mut context = BTreeMap::new();
35                    context.insert(
36                        "error_on_context_generation".to_string(),
37                        LogValue::String(err.to_string()),
38                    );
39                    record.context = context;
40                }
41            }
42        }
43
44        Ok(record)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use std::collections::BTreeMap;
51    use std::io;
52    use std::sync::Arc;
53
54    use crate::logger::{Context, Level, LogRecord};
55
56    use super::*;
57
58    fn record_with_context(context: Context) -> LogRecord {
59        LogRecord {
60            datetime: time::OffsetDateTime::now_utc(),
61            channel: "test".to_string(),
62            level: Level::Info,
63            message: "msg".to_string(),
64            context,
65            extra: BTreeMap::new(),
66        }
67    }
68
69    #[test]
70    fn resolves_deferred_map_context() {
71        let mut context = BTreeMap::new();
72        context.insert(
73            "lazy".to_string(),
74            LogValue::Deferred(Arc::new(|| {
75                let mut resolved = BTreeMap::new();
76                resolved.insert("request_id".to_string(), LogValue::from("abc"));
77                Ok(LogValue::Map(resolved))
78            })),
79        );
80
81        let record = record_with_context(context);
82        let processor = ClosureContext;
83        let processed = processor.process(record).expect("processor should resolve");
84
85        assert!(matches!(
86            processed.context.get("request_id"),
87            Some(LogValue::String(value)) if value == "abc"
88        ));
89    }
90
91    #[test]
92    fn writes_error_message_when_resolution_fails() {
93        let mut context = BTreeMap::new();
94        context.insert(
95            "lazy".to_string(),
96            LogValue::Deferred(Arc::new(|| {
97                Err(Box::new(io::Error::other("explode")) as BoxError)
98            })),
99        );
100
101        let record = record_with_context(context);
102        let processor = ClosureContext;
103        let processed = processor
104            .process(record)
105            .expect("processor should handle error");
106
107        assert!(matches!(
108            processed.context.get("error_on_context_generation"),
109            Some(LogValue::String(value)) if value.contains("explode")
110        ));
111    }
112}