use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AlertSeverity {
Info,
Warning,
Critical,
}
impl AlertSeverity {
pub fn name(&self) -> &'static str {
match self {
Self::Info => "INFO",
Self::Warning => "WARNING",
Self::Critical => "CRITICAL",
}
}
pub fn color(&self) -> &'static str {
match self {
Self::Info => "#36a64f", Self::Warning => "#ffcc00", Self::Critical => "#ff0000", }
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"INFO" => Some(Self::Info),
"WARNING" | "WARN" => Some(Self::Warning),
"CRITICAL" | "CRIT" => Some(Self::Critical),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlertChannel {
Slack { webhook_url: String },
PagerDuty { routing_key: String },
Email {
smtp_host: String,
to: String,
from: String,
},
Webhook { url: String, method: String },
Console,
}
impl AlertChannel {
pub fn name(&self) -> &'static str {
match self {
Self::Slack { .. } => "slack",
Self::PagerDuty { .. } => "pagerduty",
Self::Email { .. } => "email",
Self::Webhook { .. } => "webhook",
Self::Console => "console",
}
}
pub fn slack(webhook_url: &str) -> Self {
Self::Slack {
webhook_url: webhook_url.to_string(),
}
}
pub fn pagerduty(routing_key: &str) -> Self {
Self::PagerDuty {
routing_key: routing_key.to_string(),
}
}
pub fn webhook(url: &str) -> Self {
Self::Webhook {
url: url.to_string(),
method: "POST".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct Alert {
pub id: String,
pub title: String,
pub message: String,
pub severity: AlertSeverity,
pub source: String,
pub value: Option<f64>,
pub threshold: Option<f64>,
pub timestamp: u64,
pub metadata: HashMap<String, String>,
}
impl Alert {
pub fn new(title: &str, message: &str, severity: AlertSeverity) -> Self {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Self {
id: format!("{}_{}", title.replace(' ', "_").to_lowercase(), timestamp),
title: title.to_string(),
message: message.to_string(),
severity,
source: String::new(),
value: None,
threshold: None,
timestamp,
metadata: HashMap::new(),
}
}
pub fn with_source(mut self, source: &str) -> Self {
self.source = source.to_string();
self
}
pub fn with_value(mut self, value: f64) -> Self {
self.value = Some(value);
self
}
pub fn with_threshold(mut self, threshold: f64) -> Self {
self.threshold = Some(threshold);
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn to_slack_json(&self) -> String {
let value_str = self.value.map(|v| format!("{:.2}", v)).unwrap_or_default();
let threshold_str = self
.threshold
.map(|t| format!("{:.2}", t))
.unwrap_or_default();
format!(
r#"{{"attachments":[{{"color":"{}","title":"{}","text":"{}","fields":[{{"title":"Severity","value":"{}","short":true}},{{"title":"Source","value":"{}","short":true}},{{"title":"Value","value":"{}","short":true}},{{"title":"Threshold","value":"{}","short":true}}],"ts":{}}}]}}"#,
self.severity.color(),
self.title,
self.message,
self.severity.name(),
self.source,
value_str,
threshold_str,
self.timestamp / 1000
)
}
pub fn to_pagerduty_json(&self, routing_key: &str) -> String {
let action = match self.severity {
AlertSeverity::Critical | AlertSeverity::Warning | AlertSeverity::Info => "trigger",
};
format!(
r#"{{"routing_key":"{}","event_action":"{}","dedup_key":"{}","payload":{{"summary":"{}","source":"{}","severity":"{}","timestamp":"{}"}}}}"#,
routing_key,
action,
self.id,
self.title,
self.source,
self.severity.name().to_lowercase(),
self.timestamp
)
}
pub fn to_json(&self) -> String {
format!(
r#"{{"id":"{}","title":"{}","message":"{}","severity":"{}","source":"{}","value":{},"threshold":{},"timestamp":{}}}"#,
self.id,
self.title,
self.message,
self.severity.name(),
self.source,
self.value
.map(|v| format!("{}", v))
.unwrap_or("null".to_string()),
self.threshold
.map(|t| format!("{}", t))
.unwrap_or("null".to_string()),
self.timestamp
)
}
}
#[derive(Debug, Clone)]
pub struct DeliveryResult {
pub channel: String,
pub success: bool,
pub error: Option<String>,
pub duration_ms: u64,
}
impl DeliveryResult {
pub fn success(channel: &str, duration_ms: u64) -> Self {
Self {
channel: channel.to_string(),
success: true,
error: None,
duration_ms,
}
}
pub fn failure(channel: &str, error: &str) -> Self {
Self {
channel: channel.to_string(),
success: false,
error: Some(error.to_string()),
duration_ms: 0,
}
}
}