AkurAI Build
Menu

Public repository / c39a9a924feb8021a7355a7299f92502eb8d3e76

AkurAI-Monitor / src/main.rs

Latest change c39a9a924feb8021a7355a7299f92502eb8d3e76 - Initial commit: rust-monitor v0.1.0 by Olafur Bui

mod alert;
mod collectors;
mod config;
mod db;
mod routes;
mod schema;
mod tailer;

use axum::{routing::get, Router};
use tower_http::services::{ServeDir, ServeFile};

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("rust_monitor=info")),
        )
        .init();

    tracing::info!("rust-monitor starting up");

    let cfg = config::get();

    // Initialize DB (triggers LazyLock)
    db::with_db(|_| {});

    // Spawn metric collector task
    let interval = cfg.interval_secs;
    tokio::spawn(async move {
        let mut ticker = tokio::time::interval(std::time::Duration::from_secs(interval));
        loop {
            ticker.tick().await;
            let metrics = tokio::task::spawn_blocking(collectors::collect_all)
                .await
                .unwrap_or_default();

            let now = chrono::Utc::now().timestamp();
            let count = metrics.len();
            db::with_db(|conn| {
                for m in &metrics {
                    conn.execute(
                        "INSERT INTO metrics (name, value, ts) VALUES (?1, ?2, ?3)",
                        rusqlite::params![m.name, m.value, now],
                    )
                    .ok();
                }
            });

            tracing::debug!(count, "collected metrics");
        }
    });

    // Spawn log tailer task
    let log_files = cfg.log_files.clone();
    let tailer_interval = cfg.interval_secs;
    tokio::spawn(async move {
        let mut log_tailer = tailer::LogTailer::new(&log_files);
        let mut ticker = tokio::time::interval(std::time::Duration::from_secs(tailer_interval));
        loop {
            ticker.tick().await;
            let entries = log_tailer.read_new_lines();
            if entries.is_empty() {
                continue;
            }

            let now = chrono::Utc::now().timestamp();
            let count = entries.len();
            db::with_db(|conn| {
                for (source, line) in &entries {
                    conn.execute(
                        "INSERT INTO logs (source, line, ts) VALUES (?1, ?2, ?3)",
                        rusqlite::params![source, line, now],
                    )
                    .ok();
                }
            });

            tracing::debug!(count, "tailed log lines");
        }
    });

    // Spawn alert engine task
    let alert_log = cfg.alert_log.clone();
    tokio::spawn(async move {
        let mut ticker = tokio::time::interval(std::time::Duration::from_secs(60));
        loop {
            ticker.tick().await;
            alert::evaluate_alerts(&alert_log);
        }
    });

    // Spawn retention cleanup task
    let retention_days = cfg.retention_days;
    let log_retention_days = cfg.log_retention_days;
    tokio::spawn(async move {
        let mut ticker = tokio::time::interval(std::time::Duration::from_secs(3600));
        loop {
            ticker.tick().await;
            let now = chrono::Utc::now().timestamp();
            let metric_cutoff = now - (retention_days * 86400);
            let log_cutoff = now - (log_retention_days * 86400);

            db::with_db(|conn| {
                let m = conn
                    .execute("DELETE FROM metrics WHERE ts < ?1", rusqlite::params![metric_cutoff])
                    .unwrap_or(0);
                let l = conn
                    .execute("DELETE FROM logs WHERE ts < ?1", rusqlite::params![log_cutoff])
                    .unwrap_or(0);
                if m > 0 || l > 0 {
                    tracing::info!(metrics_deleted = m, logs_deleted = l, "retention cleanup");
                }
            });
        }
    });

    // Build the router
    let index_path = format!("{}/index.html", cfg.static_dir);
    let static_service = ServeDir::new(&cfg.static_dir)
        .append_index_html_on_directories(true)
        .fallback(ServeFile::new(&index_path));

    let app = Router::new()
        .route("/api/health", get(routes::health))
        .route("/api/status", get(routes::status))
        .route("/api/metrics", get(routes::metrics))
        .route("/api/logs", get(routes::logs))
        .route("/api/alerts", get(routes::alerts))
        .fallback_service(static_service);

    let listener = tokio::net::TcpListener::bind(&cfg.listen_addr).await.unwrap();
    tracing::info!("listening on {}", cfg.listen_addr);
    axum::serve(listener, app).await.unwrap();
}