rivet_logger/processors/
closure_context.rs1use 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}