tracing_throttle/domain/
metadata.rs

1//! Event metadata for human-readable summaries.
2//!
3//! This module provides structures for storing event details alongside signatures,
4//! allowing summaries to show what event was suppressed (not just a hash).
5
6use std::borrow::Cow;
7use std::collections::BTreeMap;
8
9/// Metadata about a log event for human-readable summaries.
10///
11/// Stores the essential details needed to understand what event was suppressed
12/// without having to correlate the signature hash with the original log.
13///
14/// Uses `Cow<'static, str>` to reduce allocations when possible, particularly
15/// for field names which are often static in tracing macros.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct EventMetadata {
18    /// Log level (e.g., "INFO", "WARN", "ERROR")
19    pub level: String,
20    /// Message template
21    pub message: String,
22    /// Target module path
23    pub target: String,
24    /// Structured fields (key-value pairs)
25    pub fields: BTreeMap<Cow<'static, str>, Cow<'static, str>>,
26}
27
28impl EventMetadata {
29    /// Create new event metadata.
30    pub fn new(
31        level: String,
32        message: String,
33        target: String,
34        fields: BTreeMap<Cow<'static, str>, Cow<'static, str>>,
35    ) -> Self {
36        Self {
37            level,
38            message,
39            target,
40            fields,
41        }
42    }
43
44    /// Format a brief description of the event for display.
45    ///
46    /// Returns a string like: `[ERROR] database::connection: Connection failed`
47    pub fn format_brief(&self) -> String {
48        format!("[{}] {}: {}", self.level, self.target, self.message)
49    }
50
51    /// Format the event with fields for detailed display.
52    ///
53    /// Returns a string like: `[ERROR] database::connection: Connection failed {error_code="TIMEOUT", retry_count="3"}`
54    pub fn format_detailed(&self) -> String {
55        if self.fields.is_empty() {
56            self.format_brief()
57        } else {
58            let fields_str: Vec<String> = self
59                .fields
60                .iter()
61                .map(|(k, v)| format!("{}=\"{}\"", k, v))
62                .collect();
63            format!("{} {{{}}}", self.format_brief(), fields_str.join(", "))
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_format_brief() {
74        let metadata = EventMetadata::new(
75            "ERROR".to_string(),
76            "Connection failed".to_string(),
77            "database::connection".to_string(),
78            BTreeMap::new(),
79        );
80
81        assert_eq!(
82            metadata.format_brief(),
83            "[ERROR] database::connection: Connection failed"
84        );
85    }
86
87    #[test]
88    fn test_format_detailed_no_fields() {
89        let metadata = EventMetadata::new(
90            "INFO".to_string(),
91            "Request processed".to_string(),
92            "api::handler".to_string(),
93            BTreeMap::new(),
94        );
95
96        assert_eq!(
97            metadata.format_detailed(),
98            "[INFO] api::handler: Request processed"
99        );
100    }
101
102    #[test]
103    fn test_format_detailed_with_fields() {
104        let mut fields = BTreeMap::new();
105        fields.insert(Cow::Borrowed("error_code"), Cow::Borrowed("TIMEOUT"));
106        fields.insert(Cow::Borrowed("retry_count"), Cow::Borrowed("3"));
107
108        let metadata = EventMetadata::new(
109            "ERROR".to_string(),
110            "Connection failed".to_string(),
111            "database::connection".to_string(),
112            fields,
113        );
114
115        let formatted = metadata.format_detailed();
116        assert!(formatted.contains("[ERROR] database::connection: Connection failed"));
117        assert!(formatted.contains("error_code=\"TIMEOUT\""));
118        assert!(formatted.contains("retry_count=\"3\""));
119    }
120}