righvalor 0.1.0

RighValor: AI Infrastructure and Applications Framework for the Far Edge
use std::sync::atomic::{AtomicU8, Ordering};

use num_enum::{FromPrimitive, IntoPrimitive};
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
use tokio::sync::OnceCell;
use tracing_subscriber::{reload::Handle, EnvFilter};

#[allow(dead_code)]
static RIGH_LOG_RELOAD_HANDLE: OnceCell<Handle<EnvFilter, tracing_subscriber::Registry>> =
    OnceCell::const_new();
#[allow(dead_code)]
static RIGH_CURRENT_LOG_LEVEL: AtomicU8 = AtomicU8::new(2); // Default to INFO

#[derive(
    Debug,
    Clone,
    Default,
    Copy,
    Serialize,
    Deserialize,
    Display,
    EnumString,
    FromPrimitive,
    IntoPrimitive,
    PartialEq,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
#[repr(u8)]
pub enum RighLogLevel {
    Error = 0,
    Warn = 1,
    #[default]
    Info = 2,
    Debug = 3,
    Trace = 4,
    Unknown = 255,
}

impl RighLogLevel {
    pub fn to_tracing_level(self) -> tracing::Level {
        match self {
            RighLogLevel::Error => tracing::Level::ERROR,
            RighLogLevel::Warn => tracing::Level::WARN,
            RighLogLevel::Info => tracing::Level::INFO,
            RighLogLevel::Debug => tracing::Level::DEBUG,
            RighLogLevel::Trace => tracing::Level::TRACE,
            RighLogLevel::Unknown => tracing::Level::INFO, // fallback
        }
    }
}

/// Set the reload handle for dynamic log level adjustment
#[allow(dead_code)]
pub async fn set_reload_handle(handle: Handle<EnvFilter, tracing_subscriber::Registry>) {
    RIGH_LOG_RELOAD_HANDLE
        .set(handle)
        .expect("Reload handle already set");
}

/// Get current log level
#[allow(dead_code)]
pub fn get_current_log_level() -> RighLogLevel {
    let level_u8 = RIGH_CURRENT_LOG_LEVEL.load(Ordering::Relaxed);
    RighLogLevel::from(level_u8)
}

/// Set log level dynamically
#[allow(dead_code)]
pub fn set_log_level(level: RighLogLevel) -> Result<(), String> {
    if level == RighLogLevel::Unknown {
        return Err("Cannot set log level to Unknown".to_string());
    }

    // Update atomic storage
    let level_u8: u8 = level.into();
    RIGH_CURRENT_LOG_LEVEL.store(level_u8, Ordering::Relaxed);

    // Update tracing filter if handle exists
    if let Some(handle) = RIGH_LOG_RELOAD_HANDLE.get() {
        let new_filter = EnvFilter::new(level.to_string().to_uppercase());
        if let Err(e) = handle.reload(new_filter) {
            return Err(format!("Failed to reload tracing filter: {e}"));
        }
        tracing::info!(
            "Log level dynamically changed to: {}",
            level.to_string().to_uppercase()
        );
        Ok(())
    } else {
        tracing::warn!("Log reload handle not initialized, level stored but not applied");
        Ok(())
    }
}

/// Initialize logging with reload capability
pub fn init_logging_with_reload() -> Handle<EnvFilter, tracing_subscriber::Registry> {
    use std::io::{stderr, IsTerminal};

    use tracing::level_filters::LevelFilter;
    use tracing_appender::{non_blocking, rolling::daily};
    use tracing_subscriber::{filter::EnvFilter, layer::SubscriberExt, Registry};

    let log_level = std::env::var("LOG")
        .ok()
        .and_then(|level| match level.to_uppercase().as_str() {
            "TRACE" => Some(tracing::Level::TRACE),
            "DEBUG" => Some(tracing::Level::DEBUG),
            "INFO" => Some(tracing::Level::INFO),
            "WARN" => Some(tracing::Level::WARN),
            "ERROR" => Some(tracing::Level::ERROR),
            _ => None,
        })
        .unwrap_or(tracing::Level::INFO);

    let dir = tracing_subscriber::filter::Directive::from(LevelFilter::from(log_level));
    let filter = vec![dir]
        .into_iter()
        .fold(EnvFilter::from_default_env(), |filter, directive| {
            filter.add_directive(directive)
        });

    let (filter, reload_handle) = tracing_subscriber::reload::Layer::new(filter);

    let identity = c"RighValor";
    let (options, facility) = Default::default();
    let syslog_option = syslog_tracing::Syslog::new(identity, options, facility);

    if let Ok(log_file) = std::env::var("RG_LOGDIR") {
        let file_appender = daily(log_file, "righvalor.log");
        let (file_writer, guard) = non_blocking(file_appender);
        Box::leak(Box::new(guard)); // bad but okay for oneshot call
        let fmt = tracing_subscriber::fmt::Layer::default()
            .with_ansi(false)
            .with_writer(file_writer)
            .with_target(false);

        let subscriber = Registry::default().with(filter).with(fmt);
        tracing::subscriber::set_global_default(subscriber)
            .expect("to set global subscriber to file");
    } else if stderr().is_terminal() || syslog_option.is_none() {
        match std::env::var("GLOG").ok() {
            Some(_) => {
                use tracing_glog::{Glog, GlogFields};

                let fmt = tracing_subscriber::fmt::Layer::default()
                    .with_ansi(true)
                    .with_writer(std::io::stderr)
                    .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
                    .fmt_fields(GlogFields::default().compact());

                let subscriber = Registry::default().with(filter).with(fmt);
                tracing::subscriber::set_global_default(subscriber)
                    .expect("to set GLOG global subscriber");
            }
            None => {
                let fmt = tracing_subscriber::fmt::Layer::default()
                    .with_ansi(true)
                    .with_writer(std::io::stderr)
                    .with_target(false);

                let subscriber = Registry::default().with(filter).with(fmt);
                tracing::subscriber::set_global_default(subscriber)
                    .expect("to set global subscriber");
            }
        }
    } else if let Some(syslog_writer) = syslog_option {
        // Not a terminal = probably background service: log to syslog
        let syslog_layer = tracing_subscriber::fmt::Layer::default()
            .with_writer(syslog_writer)
            .with_ansi(false) // syslog doesn't understand ANSI colors
            .with_target(false)
            .without_time(); // Don't print timestamps

        let subscriber = Registry::default().with(filter).with(syslog_layer);
        tracing::subscriber::set_global_default(subscriber)
            .expect("to set syslog global subscriber");
    } else {
        println!("init_logging failed!")
    }

    reload_handle
}