use std::fmt::Display;
use hsec::hsec;
use pgw::{PG, Sql};
use static_init::dynamic;
use tracing::{error, info};
#[dynamic]
static SQL_HEARTBEAT: Sql = PG.sql("SELECT fn.heartbeat($1,$2,$3,$4)");
#[dynamic]
static SQL_HEARTBEAT_ERR: Sql = PG.sql("SELECT fn.heartbeatErr($1,$2,$3,$4)");
pub enum HeartbeatResult {
Void,
Str(String),
}
impl From<()> for HeartbeatResult {
fn from(_: ()) -> Self {
HeartbeatResult::Void
}
}
impl From<&str> for HeartbeatResult {
fn from(s: &str) -> Self {
HeartbeatResult::Str(s.into())
}
}
impl From<String> for HeartbeatResult {
fn from(s: String) -> Self {
HeartbeatResult::Str(s)
}
}
pub fn heartbeat<
R: Into<HeartbeatResult> + Send,
E: Display + Send,
F: Send + Future<Output = Result<R, E>> + 'static,
>(
duration: u64,
kind: impl Into<String> + Send + 'static,
name: impl Into<String> + Send + 'static,
fut: F,
) {
tokio::spawn(async move {
let kind = kind.into();
let name = name.into();
match fut.await {
Ok(msg) => {
let msg = msg.into();
let msg = match msg {
HeartbeatResult::Void => s_::EMPTY,
HeartbeatResult::Str(s) => s,
};
if !msg.is_empty() {
info!("{kind} {name} {msg}");
}
if let Ok::<i64, _>(diff) =
xerr::ok!(pgw::q00!(&SQL_HEARTBEAT, &kind, &name, duration, msg))
{
if diff > 0 {
let title = format!("{kind} {name} 恢复 ✅");
xerr::log!(
notify_api::send(
&title,
format!("{title}\n持续时间 {}", hsec(diff as u64)),
""
)
.await
);
}
}
}
Err(err) => {
let err = err.to_string();
error!("{kind} {name} {err}");
if let Ok::<bool, _>(first_err) =
xerr::ok!(pgw::q00!(&SQL_HEARTBEAT_ERR, &kind, &name, duration, &err))
{
if first_err {
let title = format!("{kind} {name} 故障 ❌");
xerr::log!(notify_api::send(&title, format!("{title}\n{err}"), "").await);
}
}
}
}
});
}