//! systemd daemon-health alert/recovery messages. Domain half of the Alerter //! split; shared dispatch/cooldown plumbing lives in the parent module. use tracing::instrument; use super::{AlertCategory, AlertMeta, Alerter}; impl Alerter { /// Fire when a host's watched daemons transition from all-healthy into a /// degraded (crash-loop / host-wide failed unit) or down (a watched unit /// inactive or unloaded) state. Carries the issue lines that fired. #[instrument(skip_all)] pub async fn send_systemd_alert( &self, target: &str, label: &str, status: &str, issues: &[String], ) { let alert_key = format!("systemd:{target}"); let subject = format!("[PoM] {label}: daemons {status}"); let body = format!( "Target: {label} ({target})\n\ Status: {status}\n\ Issues:\n{}\n\ Instance: {}\n\ Time: {}\n\n\ - PoM", issues .iter() .map(|i| format!(" - {i}")) .collect::>() .join("\n"), self.instance_name, chrono::Utc::now().to_rfc3339(), ); let priority = if status == "down" { "critical" } else { "high" }; self.fire_failure( &subject, &body, priority, "pom-systemd", Some(target), AlertMeta { key: &alert_key, category: AlertCategory::SystemdFailure, from: None, to: Some(status), error: None, }, ) .await; } /// Fire on recovery, every watched daemon healthy again and no host-wide /// failed unit. #[instrument(skip_all)] pub async fn send_systemd_recovery(&self, target: &str, label: &str) { let alert_key = format!("systemd:{target}"); let subject = format!("[PoM] {label}: daemons recovered"); let body = format!( "Target: {label} ({target})\n\ All watched daemons are healthy.\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::SystemdRecovery, from: None, to: Some("operational"), error: None, }, ) .await; } }