use std::ffi::CString; use tracing::span::{Attributes, Record}; use tracing::{Event, Id, Metadata}; use tracing_subscriber::fmt::time::{ChronoUtc, SystemTime}; use tracing_subscriber::fmt::MakeWriter; use crate::ffi; thread_local! { pub static LOG_BUFFER: std::cell::RefCell> = const { std::cell::RefCell::new(Vec::new()) }; } #[derive(Copy, Clone, Debug)] pub struct TracingInitError; pub fn configure_logging( config: ffi::LoggingConfig, handler: ffi::Logger, ) -> Result<(), TracingInitError> { tracing::subscriber::set_global_default(adapter(config, handler)).map_err(|_| TracingInitError) } struct ThreadLocalBufferWriter; struct ThreadLocalMakeWriter; impl<'a> MakeWriter<'a> for ThreadLocalMakeWriter { type Writer = ThreadLocalBufferWriter; fn make_writer(&self) -> Self::Writer { ThreadLocalBufferWriter } } impl std::io::Write for ThreadLocalBufferWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { LOG_BUFFER.with(|vec| vec.borrow_mut().extend_from_slice(buf)); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } fn adapter( config: ffi::LoggingConfig, handler: ffi::Logger, ) -> impl tracing::Subscriber + Send + Sync + 'static { Adapter { handler, inner: config.build(), } } impl ffi::LoggingConfig { fn build(&self) -> Box { let level: tracing::Level = self.level().into(); let builder = tracing_subscriber::fmt() .with_ansi(false) .with_max_level(level) .with_level(self.print_level) .with_target(self.print_module_info) .with_writer(ThreadLocalMakeWriter); match self.time_format() { ffi::TimeFormat::None => { let builder = builder.without_time(); match self.output_format() { ffi::LogOutputFormat::Text => Box::new(builder.finish()), ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), } } ffi::TimeFormat::Rfc3339 => { let builder = builder.with_timer(ChronoUtc::default()); match self.output_format() { ffi::LogOutputFormat::Text => Box::new(builder.finish()), ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), } } ffi::TimeFormat::System => { let builder = builder.with_timer(SystemTime); match self.output_format() { ffi::LogOutputFormat::Text => Box::new(builder.finish()), ffi::LogOutputFormat::Json => Box::new(builder.json().finish()), } } } } } struct Adapter { handler: ffi::Logger, inner: Box, } impl tracing::Subscriber for Adapter { fn enabled(&self, metadata: &Metadata<'_>) -> bool { self.inner.enabled(metadata) } fn new_span(&self, span: &Attributes<'_>) -> Id { self.inner.new_span(span) } fn record(&self, span: &Id, values: &Record<'_>) { self.inner.record(span, values) } fn record_follows_from(&self, span: &Id, follows: &Id) { self.inner.record_follows_from(span, follows) } fn event(&self, event: &Event<'_>) { self.inner.event(event); if let Ok(string) = LOG_BUFFER.with(|vec| CString::new(vec.borrow().as_slice())) { self.handler .on_message((*event.metadata().level()).into(), &string); } LOG_BUFFER.with(|vec| vec.borrow_mut().clear()) } fn enter(&self, span: &Id) { self.inner.enter(span) } fn exit(&self, span: &Id) { self.inner.exit(span) } fn clone_span(&self, span: &Id) -> Id { self.inner.clone_span(span) } fn try_close(&self, span: Id) -> bool { self.inner.try_close(span) } fn current_span(&self) -> tracing_core::span::Current { self.inner.current_span() } } impl From for ffi::LogLevel { fn from(level: tracing::Level) -> Self { match level { tracing::Level::DEBUG => ffi::LogLevel::Debug, tracing::Level::TRACE => ffi::LogLevel::Trace, tracing::Level::INFO => ffi::LogLevel::Info, tracing::Level::WARN => ffi::LogLevel::Warn, tracing::Level::ERROR => ffi::LogLevel::Error, } } } impl From for tracing::Level { fn from(level: ffi::LogLevel) -> Self { match level { ffi::LogLevel::Debug => tracing::Level::DEBUG, ffi::LogLevel::Trace => tracing::Level::TRACE, ffi::LogLevel::Info => tracing::Level::INFO, ffi::LogLevel::Warn => tracing::Level::WARN, ffi::LogLevel::Error => tracing::Level::ERROR, } } }