use std::ffi::c_char;
use std::io::Write;
use std::sync::{Arc, Mutex, OnceLock};
use arc_swap::ArcSwap;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Registry};
use crate::config::LoggingConfig;
pub type LogCallback = extern "C" fn(buf: *const c_char, len: usize);
static LOG_CALLBACK: OnceLock<ArcSwap<Option<LogCallback>>> = OnceLock::new();
static LOG_GUARD: Mutex<Option<WorkerGuard>> = Mutex::new(None);
fn get_log_callback_handle() -> &'static ArcSwap<Option<LogCallback>> {
LOG_CALLBACK.get_or_init(|| ArcSwap::from(Arc::new(None)))
}
struct FfiWriter;
impl Write for FfiWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let callback_handle = get_log_callback_handle();
let callback_arc = callback_handle.load();
if let Some(callback) = **callback_arc {
let message_bytes = if buf.last() == Some(&b'\n') {
&buf[..buf.len() - 1]
} else {
buf
};
callback(message_bytes.as_ptr() as *const c_char, message_bytes.len());
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub fn init_for_ffi(config: &LoggingConfig) {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(config.log_level.as_deref().unwrap_or("info")));
let (ffi_writer, guard) = tracing_appender::non_blocking(FfiWriter);
let mut guard_handle = LOG_GUARD.lock().unwrap();
*guard_handle = Some(guard);
let layer = tracing_subscriber::fmt::layer()
.with_writer(ffi_writer)
.with_ansi(false) .with_target(true)
.with_thread_ids(true)
.with_level(true);
Registry::default().with(filter).with(layer).init();
}
pub fn shutdown_logging() {
let mut guard_handle = LOG_GUARD.lock().unwrap();
*guard_handle = None;
}
pub fn set_log_callback(callback: Option<LogCallback>) {
let callback_handle = get_log_callback_handle();
callback_handle.store(Arc::new(callback));
}