1use super::common::add_troubleshooting_info;
2use crate::events::relay::{ConnectionIssueType, DeliveryStatus, RelayConfigType, RelayEvent};
3use serde_json::{json, Map, Value};
4
5pub struct RelayJsonFormatter;
7
8impl RelayJsonFormatter {
9 pub fn new() -> Self {
10 Self
11 }
12
13 pub fn format_event(&self, event: &RelayEvent) -> Value {
15 let mut base_object = Map::new();
16
17 base_object.insert("component".to_string(), json!("relay"));
19
20 match event {
21 RelayEvent::DeliveryStatus {
22 base: _base,
23 queue_id,
24 recipient,
25 relay_host,
26 relay_ip,
27 relay_port,
28 delay,
29 delays,
30 dsn,
31 status,
32 status_description,
33 } => {
34 base_object.insert("event_type".to_string(), json!("delivery_status"));
35 base_object.insert("queue_id".to_string(), json!(queue_id));
36 base_object.insert("recipient".to_string(), json!(recipient));
37 base_object.insert("relay_host".to_string(), json!(relay_host));
38 base_object.insert("base".to_string(), json!(_base));
39 if let Some(ip) = relay_ip {
40 base_object.insert("relay_ip".to_string(), json!(ip.to_string()));
41 }
42
43 if let Some(port) = relay_port {
44 base_object.insert("relay_port".to_string(), json!(port));
45 }
46
47 base_object.insert("delay_total_seconds".to_string(), json!(delay));
48
49 let delay_details = json!({
51 "queue_wait_seconds": delays.queue_wait,
52 "connection_setup_seconds": delays.connection_setup,
53 "connection_time_seconds": delays.connection_time,
54 "transmission_time_seconds": delays.transmission_time,
55 "total_seconds": delays.total_delay(),
56 "performance_analysis": self.analyze_delays(delays)
57 });
58 base_object.insert("delay_breakdown".to_string(), delay_details);
59
60 base_object.insert("dsn_code".to_string(), json!(dsn));
61 base_object.insert(
62 "dsn_classification".to_string(),
63 json!(self.classify_dsn(dsn)),
64 );
65 base_object.insert("delivery_status".to_string(), json!(status));
66 base_object.insert("status_description".to_string(), json!(status_description));
67
68 base_object.insert(
70 "delivery_analysis".to_string(),
71 self.analyze_delivery_status(status, dsn, status_description),
72 );
73
74 base_object.insert(
76 "relay_health".to_string(),
77 self.assess_relay_health(relay_host, delay, status),
78 );
79
80 if matches!(
82 status,
83 DeliveryStatus::Deferred | DeliveryStatus::Failed | DeliveryStatus::Bounced
84 ) {
85 add_troubleshooting_info(
86 &mut base_object,
87 &format!("relay delivery {} for {}", status, relay_host),
88 );
89 }
90 }
91
92 RelayEvent::ConnectionIssue {
93 base: _base,
94 queue_id,
95 recipient,
96 relay_host,
97 relay_ip,
98 issue_type,
99 error_message,
100 } => {
101 base_object.insert("event_type".to_string(), json!("connection_issue"));
102 base_object.insert("queue_id".to_string(), json!(queue_id));
103 base_object.insert("recipient".to_string(), json!(recipient));
104 base_object.insert("relay_host".to_string(), json!(relay_host));
105 base_object.insert("base".to_string(), json!(_base));
106 if let Some(ip) = relay_ip {
107 base_object.insert("relay_ip".to_string(), json!(ip.to_string()));
108 }
109
110 base_object.insert("issue_type".to_string(), json!(issue_type));
111 base_object.insert("error_message".to_string(), json!(error_message));
112
113 base_object.insert(
115 "connection_analysis".to_string(),
116 self.analyze_connection_issue(issue_type, relay_host),
117 );
118
119 add_troubleshooting_info(
121 &mut base_object,
122 &format!("relay connection {} to {}", issue_type, relay_host),
123 );
124 }
125
126 RelayEvent::RelayConfiguration {
127 base: _base,
128 config_type,
129 details,
130 } => {
131 base_object.insert("event_type".to_string(), json!("relay_configuration"));
132 base_object.insert("config_type".to_string(), json!(config_type));
133 base_object.insert("config_details".to_string(), json!(details));
134 base_object.insert("base".to_string(), json!(_base));
135 base_object.insert(
137 "config_analysis".to_string(),
138 self.analyze_config_event(config_type, details),
139 );
140 }
141 }
142
143 base_object.insert(
145 "relay_statistics".to_string(),
146 self.get_relay_statistics(event),
147 );
148
149 Value::Object(base_object)
150 }
151
152 fn analyze_delays(&self, delays: &crate::events::relay::DelayBreakdown) -> Value {
154 let total = delays.total_delay();
155 let mut analysis = Map::new();
156
157 let performance_rating = if total < 1.0 {
159 "excellent"
160 } else if total < 5.0 {
161 "good"
162 } else if total < 30.0 {
163 "fair"
164 } else {
165 "poor"
166 };
167
168 analysis.insert("overall_rating".to_string(), json!(performance_rating));
169
170 let bottleneck = if delays.connection_time > total * 0.5 {
172 "connection_establishment"
173 } else if delays.transmission_time > total * 0.5 {
174 "data_transmission"
175 } else if delays.queue_wait > total * 0.5 {
176 "queue_processing"
177 } else {
178 "balanced"
179 };
180
181 analysis.insert("primary_bottleneck".to_string(), json!(bottleneck));
182
183 if total > 0.0 {
185 analysis.insert(
186 "phase_percentages".to_string(),
187 json!({
188 "queue_wait": (delays.queue_wait / total * 100.0).round(),
189 "connection_setup": (delays.connection_setup / total * 100.0).round(),
190 "connection_time": (delays.connection_time / total * 100.0).round(),
191 "transmission_time": (delays.transmission_time / total * 100.0).round()
192 }),
193 );
194 }
195
196 Value::Object(analysis)
197 }
198
199 fn analyze_delivery_status(
201 &self,
202 status: &DeliveryStatus,
203 dsn: &str,
204 _description: &str,
205 ) -> Value {
206 let mut analysis = Map::new();
207
208 let result_category = match status {
210 DeliveryStatus::Sent => "successful",
211 DeliveryStatus::Deferred => "temporary_failure",
212 DeliveryStatus::Bounced => "permanent_failure",
213 DeliveryStatus::Failed => "permanent_failure",
214 DeliveryStatus::Rejected => "permanent_failure",
215 };
216
217 analysis.insert("result_category".to_string(), json!(result_category));
218 analysis.insert(
219 "is_retryable".to_string(),
220 json!(matches!(status, DeliveryStatus::Deferred)),
221 );
222
223 let dsn_class = self.classify_dsn(dsn);
225 analysis.insert("dsn_class".to_string(), json!(dsn_class));
226
227 let recommended_action = match status {
229 DeliveryStatus::Sent => "none",
230 DeliveryStatus::Deferred => "monitor_retry",
231 DeliveryStatus::Bounced => "check_recipient_address",
232 DeliveryStatus::Failed => "investigate_server_config",
233 DeliveryStatus::Rejected => "verify_sender_reputation",
234 };
235
236 analysis.insert("recommended_action".to_string(), json!(recommended_action));
237
238 Value::Object(analysis)
239 }
240
241 fn assess_relay_health(&self, relay_host: &str, delay: &f64, status: &DeliveryStatus) -> Value {
243 let mut health = Map::new();
244
245 let performance_score = if *delay < 1.0 {
247 100
248 } else if *delay < 5.0 {
249 80
250 } else if *delay < 30.0 {
251 60
252 } else {
253 30
254 };
255
256 let reliability_score = match status {
258 DeliveryStatus::Sent => 100,
259 DeliveryStatus::Deferred => 70,
260 DeliveryStatus::Bounced => 30,
261 DeliveryStatus::Failed => 20,
262 DeliveryStatus::Rejected => 20,
263 };
264
265 let overall_score = (performance_score + reliability_score) / 2;
266
267 let health_status = if overall_score >= 90 {
268 "excellent"
269 } else if overall_score >= 70 {
270 "good"
271 } else if overall_score >= 50 {
272 "warning"
273 } else {
274 "critical"
275 };
276
277 health.insert("relay_host".to_string(), json!(relay_host));
278 health.insert("performance_score".to_string(), json!(performance_score));
279 health.insert("reliability_score".to_string(), json!(reliability_score));
280 health.insert("overall_score".to_string(), json!(overall_score));
281 health.insert("health_status".to_string(), json!(health_status));
282
283 Value::Object(health)
284 }
285
286 fn analyze_connection_issue(
288 &self,
289 issue_type: &ConnectionIssueType,
290 relay_host: &str,
291 ) -> Value {
292 let mut analysis = Map::new();
293
294 let (severity, likely_cause, resolution_time) = match issue_type {
295 ConnectionIssueType::LostConnection => {
296 ("medium", "network_instability", "5-30 minutes")
297 }
298 ConnectionIssueType::ConnectionTimeout => {
299 ("medium", "network_congestion", "5-60 minutes")
300 }
301 ConnectionIssueType::ConnectionRefused => {
302 ("high", "service_unavailable", "immediate to hours")
303 }
304 ConnectionIssueType::DnsResolutionFailed => {
305 ("high", "dns_configuration", "immediate to hours")
306 }
307 ConnectionIssueType::TlsHandshakeFailed => {
308 ("high", "certificate_issue", "immediate to days")
309 }
310 ConnectionIssueType::AuthenticationFailed => ("high", "credential_issue", "immediate"),
311 ConnectionIssueType::Other => ("medium", "unknown", "varies"),
312 };
313
314 analysis.insert("severity".to_string(), json!(severity));
315 analysis.insert("likely_cause".to_string(), json!(likely_cause));
316 analysis.insert(
317 "expected_resolution_time".to_string(),
318 json!(resolution_time),
319 );
320 analysis.insert("affected_relay".to_string(), json!(relay_host));
321
322 Value::Object(analysis)
323 }
324
325 fn analyze_config_event(&self, config_type: &RelayConfigType, details: &str) -> Value {
327 let mut analysis = Map::new();
328
329 let importance = match config_type {
330 RelayConfigType::RelayHostConfig => "critical",
331 RelayConfigType::TransportMapping => "high",
332 RelayConfigType::AuthConfig => "high",
333 RelayConfigType::TlsConfig => "medium",
334 };
335
336 analysis.insert("importance".to_string(), json!(importance));
337 analysis.insert("config_type".to_string(), json!(config_type));
338 analysis.insert(
339 "requires_attention".to_string(),
340 json!(details.contains("error") || details.contains("warning")),
341 );
342
343 Value::Object(analysis)
344 }
345
346 fn classify_dsn(&self, dsn: &str) -> &'static str {
348 if dsn.starts_with("2.") {
349 "success"
350 } else if dsn.starts_with("4.") {
351 "temporary_failure"
352 } else if dsn.starts_with("5.") {
353 "permanent_failure"
354 } else {
355 "unknown"
356 }
357 }
358
359 fn get_relay_statistics(&self, event: &RelayEvent) -> Value {
361 let mut stats = Map::new();
362
363 stats.insert("component".to_string(), json!("relay"));
364 stats.insert(
365 "component_description".to_string(),
366 json!("Postfix中继传输代理"),
367 );
368
369 match event {
370 RelayEvent::DeliveryStatus {
371 relay_host, status, ..
372 } => {
373 stats.insert("relay_destination".to_string(), json!(relay_host));
374 stats.insert("operation_type".to_string(), json!("mail_delivery"));
375 stats.insert("operation_result".to_string(), json!(status));
376 }
377 RelayEvent::ConnectionIssue {
378 relay_host,
379 issue_type,
380 ..
381 } => {
382 stats.insert("relay_destination".to_string(), json!(relay_host));
383 stats.insert("operation_type".to_string(), json!("connection"));
384 stats.insert("operation_result".to_string(), json!(issue_type));
385 }
386 RelayEvent::RelayConfiguration { config_type, .. } => {
387 stats.insert("operation_type".to_string(), json!("configuration"));
388 stats.insert("config_category".to_string(), json!(config_type));
389 }
390 }
391
392 Value::Object(stats)
393 }
394}
395
396impl Default for RelayJsonFormatter {
397 fn default() -> Self {
398 Self::new()
399 }
400}