sfio-tracing-ffi 0.9.0

An oo-bindgen model for using tracing in an FFI environment
Documentation
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<Vec<u8>> = 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<usize> {
        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<dyn tracing::Subscriber + Send + Sync> {
        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<dyn tracing::Subscriber + Send + Sync + 'static>,
}

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<tracing::Level> 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<ffi::LogLevel> 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,
        }
    }
}