Skip to main content

saf_core/logging/
formatter.rs

1//! DSL line formatter for SAF structured debug logging.
2//!
3//! Produces lines in the format:
4//! ```text
5//! [module::phase][tag] narrative | key=value key=value
6//! ```
7
8/// Format a complete SAF log line.
9///
10/// - `module`, `phase`, `tag`: the log coordinates
11/// - `narrative`: optional human-readable summary (empty string if keys-only)
12/// - `kv`: pre-formatted key=value pairs (empty string if narrative-only)
13pub fn format_saf_log_line(
14    module: &str,
15    phase: &str,
16    tag: &str,
17    narrative: &str,
18    kv: &str,
19) -> String {
20    let mut line = String::with_capacity(128);
21
22    // Prefix: [module::phase][tag]
23    line.push('[');
24    line.push_str(module);
25    line.push_str("::");
26    line.push_str(phase);
27    line.push_str("][");
28    line.push_str(tag);
29    line.push(']');
30
31    match (narrative.is_empty(), kv.is_empty()) {
32        // Keys only: [mod::phase][tag] | key=val
33        (true, false) => {
34            line.push_str(" | ");
35            line.push_str(kv);
36        }
37        // Narrative only: [mod::phase][tag] narrative
38        (false, true) => {
39            line.push(' ');
40            line.push_str(narrative);
41        }
42        // Full form: [mod::phase][tag] narrative | key=val
43        (false, false) => {
44            line.push(' ');
45            line.push_str(narrative);
46            line.push_str(" | ");
47            line.push_str(kv);
48        }
49        // Neither (shouldn't happen, but handle gracefully)
50        (true, true) => {}
51    }
52
53    line
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_full_form() {
62        let line = format_saf_log_line(
63            "pta",
64            "solve",
65            "worklist",
66            "pts grew",
67            "val=0x1a2b pts_size=3",
68        );
69        assert_eq!(
70            line,
71            "[pta::solve][worklist] pts grew | val=0x1a2b pts_size=3"
72        );
73    }
74
75    #[test]
76    fn test_narrative_only() {
77        let line = format_saf_log_line("pta", "solve", "convergence", "fixpoint reached", "");
78        assert_eq!(line, "[pta::solve][convergence] fixpoint reached");
79    }
80
81    #[test]
82    fn test_keys_only() {
83        let line = format_saf_log_line("pta", "solve", "stats", "", "iter=12 worklist=342");
84        assert_eq!(line, "[pta::solve][stats] | iter=12 worklist=342");
85    }
86}