use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use tokio::time::sleep;
use crate::logging::{log_info, log_success, log_warn, log_error};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorConfig {
pub url: String,
pub method: String,
pub expected_code: u16,
pub interval: u64,
pub timeout: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MonitorResult {
pub url: String,
pub status_code: u16,
pub response_time_ms: f64,
pub success: bool,
pub timestamp: i64,
pub error: Option<String>,
}
impl MonitorConfig {
pub async fn from_xbp_config() -> Result<Option<Self>> {
let config = match crate::commands::service::load_xbp_config().await {
Ok(cfg) => cfg,
Err(_) => return Ok(None),
};
if let Some(url) = config.monitor_url {
Ok(Some(MonitorConfig {
url,
method: config.monitor_method.unwrap_or_else(|| "GET".to_string()),
expected_code: config.monitor_expected_code.unwrap_or(200),
interval: config.monitor_interval.unwrap_or(60),
timeout: 30,
}))
} else {
Ok(None)
}
}
}
pub async fn run_monitor_check(config: &MonitorConfig) -> Result<MonitorResult> {
let start_time = Instant::now();
let client = Client::builder()
.timeout(Duration::from_secs(config.timeout))
.build()?;
let method = match config.method.to_uppercase().as_str() {
"GET" => reqwest::Method::GET,
"POST" => reqwest::Method::POST,
"PUT" => reqwest::Method::PUT,
"DELETE" => reqwest::Method::DELETE,
"HEAD" => reqwest::Method::HEAD,
_ => reqwest::Method::GET,
};
let result = match client.request(method, &config.url).send().await {
Ok(response) => {
let status_code = response.status().as_u16();
let duration = start_time.elapsed();
let response_time_ms = duration.as_secs_f64() * 1000.0;
let success = status_code == config.expected_code;
MonitorResult {
url: config.url.clone(),
status_code,
response_time_ms,
success,
timestamp: chrono::Utc::now().timestamp(),
error: None,
}
}
Err(e) => {
let duration = start_time.elapsed();
let response_time_ms = duration.as_secs_f64() * 1000.0;
MonitorResult {
url: config.url.clone(),
status_code: 0,
response_time_ms,
success: false,
timestamp: chrono::Utc::now().timestamp(),
error: Some(e.to_string()),
}
}
};
Ok(result)
}
pub async fn start_monitoring_loop(config: MonitorConfig) -> Result<()> {
let _ = log_info("monitor", &format!("Starting monitoring for {}", config.url), None).await;
let _ = log_info("monitor", &format!("Interval: {}s, Expected: {}", config.interval, config.expected_code), None).await;
loop {
match run_monitor_check(&config).await {
Ok(result) => {
if result.success {
let _ = log_success(
"monitor",
&format!("{} - {} - {:.2}ms", result.url, result.status_code, result.response_time_ms),
None
).await;
} else {
let error_msg = result.error.as_deref().unwrap_or("Status code mismatch");
let _ = log_error(
"monitor",
&format!("{} - FAILED", result.url),
Some(&format!("Code: {}, Error: {}", result.status_code, error_msg))
).await;
}
}
Err(e) => {
let _ = log_error("monitor", "Monitor check failed", Some(&e.to_string())).await;
}
}
sleep(Duration::from_secs(config.interval)).await;
}
}
pub async fn run_single_check() -> Result<()> {
match MonitorConfig::from_xbp_config().await? {
Some(config) => {
let _ = log_info("monitor", &format!("Checking {}", config.url), None).await;
let result = run_monitor_check(&config).await?;
if result.success {
let _ = log_success(
"monitor",
&format!("✓ {} returned {} in {:.2}ms", result.url, result.status_code, result.response_time_ms),
None
).await;
} else {
let error_msg = result.error.as_deref().unwrap_or("Status code mismatch");
let _ = log_error(
"monitor",
&format!("✗ {} check failed", result.url),
Some(&format!("Expected: {}, Got: {}, Error: {}", config.expected_code, result.status_code, error_msg))
).await;
}
Ok(())
}
None => {
let _ = log_warn("monitor", "No monitor configuration found in xbp.json", None).await;
let _ = log_info("monitor", "Add monitor_url, monitor_method, monitor_expected_code to xbp.json", None).await;
Ok(())
}
}
}
pub async fn start_monitor_daemon() -> Result<()> {
match MonitorConfig::from_xbp_config().await? {
Some(config) => {
start_monitoring_loop(config).await
}
None => {
let _ = log_error("monitor", "No monitor configuration found in xbp.json", None).await;
Err(anyhow::anyhow!("Monitor configuration required"))
}
}
}