postfix_log_parser/formatters/json/
discard.rs1use serde_json::{json, Map, Value};
2
3use crate::events::discard::{DiscardConfigType, DiscardEvent};
4
5pub struct DiscardJsonFormatter;
7
8impl DiscardJsonFormatter {
9 pub fn new() -> Self {
10 Self
11 }
12
13 pub fn format_event(&self, event: &DiscardEvent) -> Value {
14 let mut base_object = Map::new();
15
16 base_object.insert("component".to_string(), json!("discard"));
18
19 match event {
20 DiscardEvent::MessageDiscard {
21 base: _base,
22 queue_id,
23 recipient,
24 relay,
25 delay,
26 delays,
27 dsn,
28 status,
29 discard_reason,
30 } => {
31 base_object.insert("event_type".to_string(), json!("message_discard"));
32 base_object.insert("queue_id".to_string(), json!(queue_id));
33 base_object.insert("recipient".to_string(), json!(recipient));
34 base_object.insert("relay".to_string(), json!(relay));
35 base_object.insert("base".to_string(), json!(_base));
36
37 base_object.insert("delay_total_seconds".to_string(), json!(delay));
38
39 let delay_details = json!({
41 "queue_wait_seconds": delays.queue_wait,
42 "connection_setup_seconds": delays.connection_setup,
43 "connection_time_seconds": delays.connection_time,
44 "transmission_time_seconds": delays.transmission_time,
45 "total_seconds": delays.total_delay(),
46 "is_fast_discard": delays.is_fast_discard(),
47 "performance_analysis": self.analyze_discard_performance(delays)
48 });
49 base_object.insert("delay_breakdown".to_string(), delay_details);
50
51 base_object.insert("dsn_code".to_string(), json!(dsn));
52 base_object.insert(
53 "dsn_classification".to_string(),
54 json!(self.classify_dsn(dsn)),
55 );
56 base_object.insert("delivery_status".to_string(), json!(status));
57 base_object.insert("discard_reason".to_string(), json!(discard_reason));
58
59 base_object.insert(
61 "discard_analysis".to_string(),
62 self.analyze_discard_operation(dsn, status, discard_reason),
63 );
64
65 base_object.insert(
67 "mail_flow_analysis".to_string(),
68 self.analyze_mail_flow(recipient, discard_reason),
69 );
70
71 base_object.insert(
73 "security_assessment".to_string(),
74 self.assess_security_implications(recipient, discard_reason),
75 );
76 }
77
78 DiscardEvent::Configuration {
79 base: _base,
80 config_type,
81 details,
82 } => {
83 base_object.insert("event_type".to_string(), json!("configuration"));
84 base_object.insert("config_type".to_string(), json!(config_type));
85 base_object.insert("config_details".to_string(), json!(details));
86 base_object.insert("base".to_string(), json!(_base));
87
88 base_object.insert(
90 "config_analysis".to_string(),
91 self.analyze_config_event(config_type, details),
92 );
93 }
94 }
95
96 base_object.insert(
98 "discard_statistics".to_string(),
99 self.get_discard_statistics(event),
100 );
101
102 Value::Object(base_object)
103 }
104
105 fn analyze_discard_performance(
107 &self,
108 delays: &crate::events::discard::DelayBreakdown,
109 ) -> Value {
110 let total = delays.total_delay();
111 let mut analysis = Map::new();
112
113 let performance_rating = if total < 0.01 {
115 "instant"
116 } else if total < 0.05 {
117 "very_fast"
118 } else if total < 0.1 {
119 "fast"
120 } else {
121 "slow"
122 };
123
124 analysis.insert("overall_rating".to_string(), json!(performance_rating));
125
126 let delay_source = if delays.queue_wait > total * 0.8 {
128 "queue_processing"
129 } else if total > 0.1 {
130 "system_load"
131 } else {
132 "normal_processing"
133 };
134
135 analysis.insert("primary_delay_source".to_string(), json!(delay_source));
136
137 analysis.insert(
139 "efficiency_metrics".to_string(),
140 json!({
141 "queue_wait_percentage": if total > 0.0 { (delays.queue_wait / total * 100.0).round() } else { 0.0 },
142 "is_optimal": delays.is_fast_discard(),
143 "expected_range": "0.00-0.05 seconds for optimal performance"
144 }),
145 );
146
147 Value::Object(analysis)
148 }
149
150 fn analyze_discard_operation(&self, dsn: &str, status: &str, discard_reason: &str) -> Value {
152 let mut analysis = Map::new();
153
154 let operation_validity = if status == "sent" && dsn.starts_with("2.") {
156 "successful_discard"
157 } else {
158 "abnormal_discard"
159 };
160
161 analysis.insert("operation_status".to_string(), json!(operation_validity));
162
163 let dsn_class = self.classify_dsn(dsn);
165 analysis.insert("dsn_classification".to_string(), json!(dsn_class));
166
167 let reason_category = if discard_reason.contains(".") {
169 "domain_based"
170 } else if discard_reason.contains("spam") || discard_reason.contains("reject") {
171 "security_based"
172 } else if discard_reason.contains("policy") {
173 "policy_based"
174 } else {
175 "configuration_based"
176 };
177
178 analysis.insert("discard_category".to_string(), json!(reason_category));
179
180 let recommended_action = match reason_category {
182 "security_based" => "monitor_for_patterns",
183 "policy_based" => "review_policy_rules",
184 "domain_based" => "verify_domain_configuration",
185 _ => "none_required",
186 };
187
188 analysis.insert("recommended_action".to_string(), json!(recommended_action));
189
190 Value::Object(analysis)
191 }
192
193 fn analyze_mail_flow(&self, recipient: &str, discard_reason: &str) -> Value {
195 let mut analysis = Map::new();
196
197 let recipient_domain = recipient.split('@').nth(1).unwrap_or("unknown");
199
200 analysis.insert("recipient_domain".to_string(), json!(recipient_domain));
201
202 let traffic_type = if discard_reason == recipient_domain {
204 "domain_redirect"
205 } else if discard_reason.contains("blackhole") {
206 "spam_filtering"
207 } else if discard_reason.contains("test") {
208 "testing_traffic"
209 } else {
210 "policy_discard"
211 };
212
213 analysis.insert("traffic_type".to_string(), json!(traffic_type));
214
215 let impact_level = match traffic_type {
217 "spam_filtering" => "positive",
218 "testing_traffic" => "neutral",
219 "policy_discard" => "informational",
220 _ => "informational",
221 };
222
223 analysis.insert("impact_level".to_string(), json!(impact_level));
224
225 Value::Object(analysis)
226 }
227
228 fn assess_security_implications(&self, _recipient: &str, discard_reason: &str) -> Value {
230 let mut assessment = Map::new();
231
232 let security_level = if discard_reason.contains("spam")
234 || discard_reason.contains("virus")
235 || discard_reason.contains("malware")
236 {
237 "high_security_event"
238 } else if discard_reason.contains("policy") || discard_reason.contains("filter") {
239 "policy_enforcement"
240 } else {
241 "normal_operation"
242 };
243
244 assessment.insert("security_level".to_string(), json!(security_level));
245
246 let threat_indicators = json!({
248 "potential_spam": discard_reason.contains("spam"),
249 "potential_malware": discard_reason.contains("virus") || discard_reason.contains("malware"),
250 "policy_violation": discard_reason.contains("policy"),
251 "suspicious_domain": discard_reason.contains("suspicious")
252 });
253
254 assessment.insert("threat_indicators".to_string(), threat_indicators);
255
256 let monitoring_recommendation = match security_level {
258 "high_security_event" => "increase_monitoring",
259 "policy_enforcement" => "log_for_compliance",
260 _ => "standard_logging",
261 };
262
263 assessment.insert(
264 "monitoring_recommendation".to_string(),
265 json!(monitoring_recommendation),
266 );
267
268 Value::Object(assessment)
269 }
270
271 fn analyze_config_event(&self, config_type: &DiscardConfigType, details: &str) -> Value {
273 let mut analysis = Map::new();
274
275 let config_impact = match config_type {
277 DiscardConfigType::ServiceStartup => "service_lifecycle",
278 DiscardConfigType::TransportMapping => "routing_configuration",
279 DiscardConfigType::DiscardRules => "filtering_rules",
280 DiscardConfigType::Other => "general_configuration",
281 };
282
283 analysis.insert("configuration_impact".to_string(), json!(config_impact));
284
285 let operation_type = if details.contains("starting") {
287 "service_start"
288 } else if details.contains("stopping") {
289 "service_stop"
290 } else if details.contains("warning") {
291 "configuration_warning"
292 } else {
293 "configuration_change"
294 };
295
296 analysis.insert("operation_type".to_string(), json!(operation_type));
297
298 let importance_level = match config_type {
300 DiscardConfigType::ServiceStartup => "high",
301 DiscardConfigType::DiscardRules => "medium",
302 _ => "low",
303 };
304
305 analysis.insert("importance_level".to_string(), json!(importance_level));
306
307 Value::Object(analysis)
308 }
309
310 fn classify_dsn(&self, dsn: &str) -> &'static str {
312 match dsn {
313 "2.0.0" => "successful_discard",
314 "2.1.5" => "destination_valid",
315 "2.7.0" => "delivery_successful",
316 _ if dsn.starts_with("2.") => "success_code",
317 _ if dsn.starts_with("4.") => "temporary_failure",
318 _ if dsn.starts_with("5.") => "permanent_failure",
319 _ => "unknown_dsn",
320 }
321 }
322
323 fn get_discard_statistics(&self, event: &DiscardEvent) -> Value {
325 json!({
326 "event_category": match event {
327 DiscardEvent::MessageDiscard { .. } => "message_discard",
328 DiscardEvent::Configuration { .. } => "configuration"
329 },
330 "processing_status": "completed",
331 "component_health": "operational",
332 "typical_use_cases": [
333 "spam_filtering",
334 "policy_enforcement",
335 "content_filtering",
336 "testing_scenarios"
337 ]
338 })
339 }
340}
341
342impl Default for DiscardJsonFormatter {
343 fn default() -> Self {
344 Self::new()
345 }
346}