Skip to content

Commit c123ed8

Browse files
authored
Clean up alerts and add labels for alertmanager target (#315)
- Add versioning to alert json - Add symbols =% (contains) and !% (doesn't contain) variant of string rule - Fix resolved annotation for alertmanager - Add additional labels as discussed in #311 Fixes #311
1 parent 9cb7c7f commit c123ed8

File tree

4 files changed

+69
-6
lines changed

4 files changed

+69
-6
lines changed

server/src/alerts/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,17 @@ use self::target::Target;
3333
#[derive(Default, Debug, Serialize, Deserialize)]
3434
#[serde(rename_all = "camelCase")]
3535
pub struct Alerts {
36+
pub version: AlertVerison,
3637
pub alerts: Vec<Alert>,
3738
}
3839

40+
#[derive(Default, Debug, Serialize, Deserialize)]
41+
#[serde(rename_all = "lowercase")]
42+
pub enum AlertVerison {
43+
#[default]
44+
V1,
45+
}
46+
3947
#[derive(Debug, Serialize, Deserialize)]
4048
#[serde(rename_all = "camelCase")]
4149
pub struct Alert {
@@ -54,7 +62,7 @@ impl Alert {
5462
match resolves {
5563
AlertState::Listening | AlertState::Firing => (),
5664
alert_state @ (AlertState::SetToFiring | AlertState::Resolved) => {
57-
let context = self.get_context(stream_name, alert_state);
65+
let context = self.get_context(stream_name, alert_state, &self.rule);
5866
ALERTS_STATES
5967
.with_label_values(&[
6068
context.stream.as_str(),
@@ -69,15 +77,28 @@ impl Alert {
6977
}
7078
}
7179

72-
fn get_context(&self, stream_name: String, alert_state: AlertState) -> Context {
80+
fn get_context(&self, stream_name: String, alert_state: AlertState, rule: &Rule) -> Context {
7381
let deployment_id = storage::StorageMetadata::global().deployment_id;
82+
let additional_labels =
83+
serde_json::to_value(rule).expect("rule is perfectly deserializable");
84+
let mut flatten_additional_labels = serde_json::json!({});
85+
flatten_json::flatten(
86+
&additional_labels,
87+
&mut flatten_additional_labels,
88+
Some("rule".to_string()),
89+
false,
90+
Some("_"),
91+
)
92+
.expect("can be flattened");
93+
7494
Context::new(
7595
stream_name,
7696
self.name.clone(),
7797
self.message.clone(),
7898
self.rule.trigger_reason(),
7999
alert_state,
80100
deployment_id,
101+
flatten_additional_labels,
81102
)
82103
}
83104
}
@@ -94,6 +115,7 @@ pub struct Context {
94115
reason: String,
95116
alert_state: AlertState,
96117
deployment_id: uid::Uid,
118+
additional_labels: serde_json::Value,
97119
}
98120

99121
impl Context {
@@ -104,6 +126,7 @@ impl Context {
104126
reason: String,
105127
alert_state: AlertState,
106128
deployment_id: uid::Uid,
129+
additional_labels: serde_json::Value,
107130
) -> Self {
108131
Self {
109132
stream,
@@ -112,6 +135,7 @@ impl Context {
112135
reason,
113136
alert_state,
114137
deployment_id,
138+
additional_labels,
115139
}
116140
}
117141

server/src/alerts/rule.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,11 @@ pub mod base {
408408
Exact,
409409
#[serde(alias = "!=")]
410410
NotExact,
411+
#[serde(alias = "=%")]
411412
Contains,
413+
#[serde(alias = "!%")]
412414
NotContains,
415+
// =~ and !~ reserved for regex
413416
}
414417

415418
impl Default for StringOperator {

server/src/alerts/target.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ use humantime_serde::re::humantime;
3030
use reqwest::ClientBuilder;
3131
use serde::{Deserialize, Serialize};
3232

33+
use crate::utils::json;
34+
3335
use super::{AlertState, CallableTarget, Context};
3436

3537
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@@ -313,7 +315,7 @@ impl CallableTarget for AlertManager {
313315
.build()
314316
.expect("Client can be constructed on this system");
315317

316-
let mut alert = serde_json::json!([{
318+
let mut alerts = serde_json::json!([{
317319
"labels": {
318320
"alertname": payload.alert_name,
319321
"stream": payload.stream,
@@ -325,20 +327,34 @@ impl CallableTarget for AlertManager {
325327
}
326328
}]);
327329

330+
let alert = &mut alerts[0];
331+
332+
alert["labels"].as_object_mut().expect("is object").extend(
333+
payload
334+
.additional_labels
335+
.as_object()
336+
.expect("is object")
337+
.iter()
338+
// filter non null values for alertmanager and only pass strings
339+
.filter(|(_, value)| !value.is_null())
340+
.map(|(k, value)| (k.to_owned(), json::convert_to_string(value))),
341+
);
342+
328343
// fill in status label accordingly
329344
match payload.alert_state {
330-
AlertState::SetToFiring => alert[0]["labels"]["status"] = "firing".into(),
345+
AlertState::SetToFiring => alert["labels"]["status"] = "firing".into(),
331346
AlertState::Resolved => {
332-
let alert = &mut alert[0];
333347
alert["labels"]["status"] = "resolved".into();
348+
alert["annotations"]["reason"] =
349+
serde_json::Value::String(payload.default_resolved_string());
334350
alert["endsAt"] = Utc::now()
335351
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
336352
.into();
337353
}
338354
_ => unreachable!(),
339355
};
340356

341-
if let Err(e) = client.post(&self.endpoint).json(&alert).send().await {
357+
if let Err(e) = client.post(&self.endpoint).json(&alerts).send().await {
342358
log::error!("Couldn't make call to alertmanager, error: {}", e)
343359
}
344360
}

server/src/utils/json.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,23 @@ pub fn merge(value: &mut Value, fields: impl Iterator<Item = (String, Value)>) {
4040
}
4141
}
4242
}
43+
44+
pub fn convert_to_string(value: &Value) -> Value {
45+
match value {
46+
Value::Null => Value::String("null".to_owned()),
47+
Value::Bool(b) => Value::String(b.to_string()),
48+
Value::Number(n) => Value::String(n.to_string()),
49+
Value::String(s) => Value::String(s.to_owned()),
50+
Value::Array(v) => {
51+
let new_vec = v.iter().map(convert_to_string).collect();
52+
Value::Array(new_vec)
53+
}
54+
Value::Object(map) => {
55+
let new_map = map
56+
.iter()
57+
.map(|(k, v)| (k.clone(), convert_to_string(v)))
58+
.collect();
59+
Value::Object(new_map)
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)