Skip to main content

native/kameleoon_elixir_bridge/src/logging_ex.rs

use kameleoon_core::logging::{DefaultLogger, KameleoonLogger, LogLevel, Logger};
use rustler::{types::LocalPid, Atom, Error as NifError, NifResult};

use crate::client_ex::ok;

rustler::atoms! {
    kameleoon_log,
    none,
    error,
    warning,
    info,
    debug,
}

struct LoggerEx {
    owner: LocalPid,
}

impl Logger for LoggerEx {
    fn log(&self, log_level: LogLevel, message: &str) {
        send_log(self.owner, log_level, message.to_owned());
    }
}

#[rustler::nif]
fn logger_set_handler(owner: Option<LocalPid>) -> Atom {
    match owner {
        Some(owner) => KameleoonLogger::set_logger(Box::new(LoggerEx { owner })),
        None => KameleoonLogger::set_logger(Box::new(DefaultLogger {})),
    }
    ok()
}

#[rustler::nif]
fn logger_set_log_level(level: u8) -> NifResult<Atom> {
    let level = decode_log_level(level)?;
    KameleoonLogger::set_log_level(level);
    Ok(ok())
}

fn decode_log_level(level: u8) -> NifResult<LogLevel> {
    if level <= LogLevel::Debug as u8 {
        Ok(LogLevel::from(level))
    } else {
        Err(NifError::BadArg)
    }
}

fn send_log(owner: LocalPid, log_level: LogLevel, message: String) {
    let send = move || {
        let mut env = rustler::OwnedEnv::new();
        let _ = env.send_and_clear(&owner, |_env| (kameleoon_log(), encode_log_level(log_level), message));
    };

    if rustler::thread::is_scheduler_thread() {
        // Prefer spawning on the existing Tokio runtime to avoid new thread overhead.
        // Fall back to a plain thread only if no runtime is available.
        match tokio::runtime::Handle::try_current() {
            Ok(handle) => {
                handle.spawn(async move {
                    send();
                });
            }
            Err(_) => {
                std::thread::spawn(send);
            }
        }
    } else {
        send();
    }
}

fn encode_log_level(log_level: LogLevel) -> Atom {
    match log_level {
        LogLevel::None => none(),
        LogLevel::Error => error(),
        LogLevel::Warning => warning(),
        LogLevel::Info => info(),
        LogLevel::Debug => debug(),
    }
}