Skip to content

Commit 11e48cc

Browse files
authored
added telegram alert (#62)
* added telegram alert * added telegram alert * added minimum severity for alert
1 parent aa483de commit 11e48cc

File tree

9 files changed

+225
-18
lines changed

9 files changed

+225
-18
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ async-trait = "0.1.88"
2929
chrono = "0.4.41"
3030
prost = "0.13"
3131
rlimit = "0.10.2"
32+
telegrama-rs = "0.1.0"
3233

3334
[build-dependencies]
3435
vergen = { version = "8.0.0", features = ["build", "git", "gitcl"] }

config.toml.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ transfer_limit = 0
3131
slack_webhook = "example"
3232
channel = "example"
3333
mentions = ["@handle"]
34+
minimum_severity = "Low"
3435

3536
[[ibc]]
3637
alias = "example"

src/alerts/log.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub struct Log {
1010

1111
#[async_trait]
1212
impl AlertTrait for Log {
13-
async fn send_alerts(&self, alert: Alert) -> Result<String, String> {
13+
async fn send_alerts(&self, alert: Alert) -> Result<Option<String>, String> {
1414
let title = alert.title.clone();
1515
let description = alert.description.clone();
1616
let metadata = alert.metadata.clone();
@@ -32,7 +32,7 @@ impl AlertTrait for Log {
3232
println!("{}: {}", text.0, text.1);
3333
}
3434

35-
Ok(alert.check_id)
35+
Ok(Some(alert.check_id))
3636
}
3737

3838
async fn send_resolve(&self, alert: Alert, date: &str) -> Result<(), String> {

src/alerts/mod.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod log;
22
pub mod slack;
3+
pub mod telegram;
34

45
use async_trait::async_trait;
56
use chrono::{DateTime, Utc};
@@ -19,24 +20,38 @@ pub struct AlertManager {
1920
impl AlertManager {
2021
pub fn new(app_config: &AppConfig) -> Self {
2122
let config = app_config.get_config();
23+
let any_alert_config = config.slack.is_some() || config.telegram.is_some();
24+
25+
if !any_alert_config {
26+
return Self {
27+
communication: vec![Box::new(Log::new(config.block_explorer.clone()))],
28+
on_fire: TtlCache::new(100),
29+
};
30+
};
31+
32+
let mut alerts: Vec<Box<dyn AlertTrait>> = vec![];
33+
2234
if let Some(slack_config) = config.slack.clone() {
2335
let slack_alert = slack::SlackAlert::new(
2436
config.block_explorer.clone(),
2537
slack_config,
2638
app_config.chain_id.clone(),
2739
);
28-
Self {
29-
communication: vec![
30-
Box::new(slack_alert),
31-
Box::new(Log::new(config.block_explorer.clone())),
32-
],
33-
on_fire: TtlCache::new(100),
34-
}
35-
} else {
36-
Self {
37-
communication: vec![Box::new(Log::new(config.block_explorer.clone()))],
38-
on_fire: TtlCache::new(100),
39-
}
40+
alerts.push(Box::new(slack_alert));
41+
};
42+
43+
if let Some(telegram_config) = config.telegram.clone() {
44+
let telegram_alert = telegram::TelegramAlert::new(
45+
config.block_explorer.clone(),
46+
telegram_config,
47+
app_config.chain_id.clone(),
48+
);
49+
alerts.push(Box::new(telegram_alert));
50+
};
51+
52+
Self {
53+
communication: alerts,
54+
on_fire: TtlCache::new(100),
4055
}
4156
}
4257

@@ -101,7 +116,7 @@ impl AlertManager {
101116

102117
#[async_trait]
103118
pub trait AlertTrait: Send + Sync {
104-
async fn send_alerts(&self, alert: Alert) -> Result<String, String>;
119+
async fn send_alerts(&self, alert: Alert) -> Result<Option<String>, String>;
105120
async fn send_resolve(&self, alert: Alert, date: &str) -> Result<(), String>;
106121
fn get_block_explorer(&self) -> BlockExplorer;
107122
fn get_id(&self) -> String;

src/alerts/slack.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ pub struct SlackAlert {
4343
pub channel: String,
4444
pub network_id: String,
4545
pub mentions: Vec<String>,
46+
pub minimum_severity: Option<Severity>,
4647
}
4748

4849
#[async_trait]
4950
impl AlertTrait for SlackAlert {
50-
async fn send_alerts(&self, alert: Alert) -> Result<String, String> {
51+
async fn send_alerts(&self, alert: Alert) -> Result<Option<String>, String> {
52+
if alert.severity < self.minimum_severity.clone().unwrap_or(Severity::Low) {
53+
return Ok(None);
54+
}
55+
5156
let block_explorer = self.block_explorer.clone();
5257

5358
let title = alert.title.clone();
@@ -109,7 +114,7 @@ impl AlertTrait for SlackAlert {
109114
.await;
110115

111116
match res {
112-
Ok(response) if response.status().is_success() => return Ok(alert.check_id),
117+
Ok(response) if response.status().is_success() => return Ok(Some(alert.check_id)),
113118
Ok(response) => {
114119
return Err(format!(
115120
"Failed to send slack alert, status: {:?}",
@@ -189,6 +194,7 @@ impl SlackAlert {
189194
channel: slack_config.channel,
190195
network_id,
191196
mentions: slack_config.mentions,
197+
minimum_severity: slack_config.minimum_severity,
192198
}
193199
}
194200
}

src/alerts/telegram.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use anyhow::Context;
2+
use async_trait::async_trait;
3+
use telegrama_rs::{ClientOptions, FormattingOptions, Response, Telegrama};
4+
5+
use crate::{
6+
alerts::AlertTrait,
7+
shared::{
8+
alert::{Alert, Severity},
9+
block_explorer::BlockExplorer,
10+
config::TelegramAlertConfig,
11+
},
12+
};
13+
14+
pub struct TelegramAlert {
15+
pub block_explorer: BlockExplorer,
16+
pub telegram_token: String,
17+
pub telegram_chat_id: String,
18+
pub network_id: String,
19+
pub minimum_severity: Option<Severity>,
20+
}
21+
22+
#[async_trait]
23+
impl AlertTrait for TelegramAlert {
24+
async fn send_alerts(&self, alert: Alert) -> Result<Option<String>, String> {
25+
if alert.severity < self.minimum_severity.clone().unwrap_or(Severity::Low) {
26+
return Ok(None);
27+
}
28+
29+
let block_explorer = self.block_explorer.clone();
30+
31+
let title = alert.title.clone();
32+
let description = alert.description.clone();
33+
let metadata = alert.metadata.clone();
34+
35+
let message = format!(
36+
"*{title} - {network}*\n\
37+
{description}.\n\
38+
*Block*: {height}\n\
39+
*Transaction*: {transaction}",
40+
title = title,
41+
description = description,
42+
height = metadata
43+
.clone()
44+
.block_height
45+
.map(|h| format!("<{}|{}>", block_explorer.get_block_url(h), h))
46+
.unwrap_or_else(|| "N/A".to_string()),
47+
transaction = metadata
48+
.clone()
49+
.tx_id
50+
.map(|tx| format!("<{}|{}>", block_explorer.get_tx_url(&tx), tx))
51+
.unwrap_or_else(|| "N/A".to_string()),
52+
network = self.network_id,
53+
);
54+
55+
let res = self.send_telegram_message(message).await;
56+
57+
match res {
58+
Ok(response) if response.ok => return Ok(Some(alert.check_id)),
59+
Ok(response) => {
60+
return Err(format!(
61+
"Failed to send telegram alert, status: {}",
62+
response.description.unwrap_or_default()
63+
));
64+
}
65+
Err(err) => {
66+
return Err(format!("Failed to send telegram alert: {}", err));
67+
}
68+
}
69+
}
70+
71+
async fn send_resolve(&self, alert: Alert, date: &str) -> Result<(), String> {
72+
let title = alert.title.clone();
73+
74+
let message = format!(
75+
"*{title} - {network}*\n\
76+
Issue from {data} was resolved.",
77+
title = title,
78+
data = date,
79+
network = self.network_id,
80+
);
81+
82+
let res = self.send_telegram_message(message).await;
83+
84+
match res {
85+
Ok(response) if response.ok => return Ok(()),
86+
Ok(response) => {
87+
return Err(format!(
88+
"Failed to send slack alert, status: {}",
89+
response.description.unwrap_or_default()
90+
));
91+
}
92+
Err(err) => {
93+
return Err(format!("Failed to send slack alert: {}", err));
94+
}
95+
}
96+
}
97+
98+
fn get_block_explorer(&self) -> BlockExplorer {
99+
self.block_explorer.clone()
100+
}
101+
102+
fn get_id(&self) -> String {
103+
"telegram".to_string()
104+
}
105+
}
106+
107+
impl TelegramAlert {
108+
pub fn new(
109+
block_explorer: BlockExplorer,
110+
telegram_config: TelegramAlertConfig,
111+
network_id: String,
112+
) -> Self {
113+
Self {
114+
block_explorer,
115+
telegram_token: telegram_config.telegram_token,
116+
telegram_chat_id: telegram_config.telegram_chat_id,
117+
network_id,
118+
minimum_severity: telegram_config.minimum_severity,
119+
}
120+
}
121+
122+
async fn send_telegram_message(&self, message: String) -> anyhow::Result<Response> {
123+
Telegrama::configure(|config| {
124+
config.set_bot_token(&self.telegram_token);
125+
config.set_chat_id(&self.telegram_chat_id);
126+
127+
config.set_default_parse_mode("MarkdownV2"); // or "HTML"
128+
config.set_disable_web_page_preview(true);
129+
130+
let formatting = FormattingOptions {
131+
escape_markdown: true,
132+
obfuscate_emails: true,
133+
escape_html: false,
134+
truncate: Some(4096),
135+
};
136+
config.set_formatting_options(formatting);
137+
138+
let client_options = ClientOptions {
139+
timeout: 30,
140+
retry_count: 3,
141+
retry_delay: 1,
142+
};
143+
config.set_client_options(client_options);
144+
});
145+
146+
Telegrama::send_message(
147+
message,
148+
&[
149+
("parse_mode", "MarkdownV2"),
150+
("disable_web_page_preview", "true"),
151+
],
152+
)
153+
.context("sending telegram message")
154+
}
155+
}

src/shared/alert.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::time::Duration;
22

3-
#[derive(Default, Debug, Clone)]
3+
use serde::Deserialize;
4+
5+
#[derive(Default, Debug, Clone, Deserialize, PartialEq, Eq, PartialOrd)]
46
pub enum Severity {
57
#[default]
68
Low,

src/shared/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use serde::Deserialize;
22

3+
use crate::shared::alert::Severity;
4+
35
use super::block_explorer::BlockExplorer;
46

57
#[derive(Debug, Clone, Deserialize)]
@@ -11,6 +13,7 @@ pub struct Config {
1113
pub ibcs: Vec<Ibc>,
1214
pub tokens: Vec<TokenConfig>,
1315
pub slack: Option<SlackAlertConfig>,
16+
pub telegram: Option<TelegramAlertConfig>,
1417
}
1518

1619
#[derive(Debug, Clone, Deserialize)]
@@ -58,6 +61,14 @@ pub struct SlackAlertConfig {
5861
pub slack_webhook: String,
5962
pub channel: String,
6063
pub mentions: Vec<String>,
64+
pub minimum_severity: Option<Severity>,
65+
}
66+
67+
#[derive(Debug, Clone, Deserialize)]
68+
pub struct TelegramAlertConfig {
69+
pub telegram_token: String,
70+
pub telegram_chat_id: String,
71+
pub minimum_severity: Option<Severity>,
6172
}
6273

6374
impl Config {

0 commit comments

Comments
 (0)