//! health alert/recovery messages. Domain half of the Alerter split; //! shared dispatch/cooldown plumbing lives in the parent module. use std::fmt::Write as _; use tracing::instrument; use super::{AlertCategory, AlertMeta, Alerter, health_status_priority}; impl Alerter { #[instrument(skip_all)] pub async fn send_health_alert( &self, target: &str, label: &str, from_status: &str, to_status: &str, error: Option<&str>, ) { let alert_key = format!("health:{target}"); let subject = format!("[PoM] {target}: {from_status} -> {to_status}"); let mut body = format!( "Target: {label} ({target})\n\ Status: {from_status} -> {to_status}\n\ Instance: {}\n\ Time: {}\n", self.instance_name, chrono::Utc::now().to_rfc3339(), ); if let Some(err) = error { let _ = writeln!(body, "Error: {err}"); } body.push_str("\n- PoM"); let priority = health_status_priority(to_status); self.fire_failure( &subject, &body, priority, "pom-health", Some(target), AlertMeta { key: &alert_key, category: AlertCategory::Health, from: Some(from_status), to: Some(to_status), error, }, ) .await; } #[instrument(skip_all)] pub async fn send_health_recovery(&self, target: &str, label: &str, from_status: &str) { let alert_key = format!("health:{target}"); let subject = format!("[PoM] {target}: recovered"); let body = format!( "Target: {label} ({target})\n\ Status: {from_status} -> operational\n\ Instance: {}\n\ Time: {}\n\n\ - PoM", self.instance_name, chrono::Utc::now().to_rfc3339(), ); self.fire_recovery( &subject, &body, AlertMeta { key: &alert_key, category: AlertCategory::Recovery, from: Some(from_status), to: Some("operational"), error: None, }, ) .await; } }