pkgcraft/
logging.rs

1use std::cell::RefCell;
2use std::ffi::{c_char, CString};
3use std::fmt::{Debug, Write};
4
5use tracing::field::{Field, Visit};
6use tracing::{subscriber::DefaultGuard, Event, Level, Subscriber};
7use tracing_subscriber::filter::{EnvFilter, LevelFilter};
8use tracing_subscriber::{prelude::*, registry::Registry, Layer};
9
10use crate::macros::*;
11
12#[derive(Debug, Clone)]
13#[repr(C)]
14pub enum LogLevel {
15    Off,
16    Trace,
17    Debug,
18    Info,
19    Warn,
20    Error,
21}
22
23impl<'a> From<&'a Level> for LogLevel {
24    fn from(level: &'a Level) -> Self {
25        match *level {
26            Level::TRACE => Self::Trace,
27            Level::DEBUG => Self::Debug,
28            Level::INFO => Self::Info,
29            Level::WARN => Self::Warn,
30            Level::ERROR => Self::Error,
31        }
32    }
33}
34
35impl From<LogLevel> for LevelFilter {
36    fn from(level: LogLevel) -> Self {
37        match level {
38            LogLevel::Off => LevelFilter::OFF,
39            LogLevel::Trace => LevelFilter::TRACE,
40            LogLevel::Debug => LevelFilter::DEBUG,
41            LogLevel::Info => LevelFilter::INFO,
42            LogLevel::Warn => LevelFilter::WARN,
43            LogLevel::Error => LevelFilter::ERROR,
44        }
45    }
46}
47
48#[repr(C)]
49pub struct PkgcraftLog {
50    message: *mut c_char,
51    level: LogLevel,
52}
53
54impl<'a> From<&'a Event<'_>> for PkgcraftLog {
55    fn from(event: &'a Event<'_>) -> Self {
56        let mut message = String::new();
57        let mut visitor = MessageVisitor { message: &mut message };
58        event.record(&mut visitor);
59
60        Self {
61            message: try_ptr_from_str!(message),
62            level: event.metadata().level().into(),
63        }
64    }
65}
66
67impl Drop for PkgcraftLog {
68    fn drop(&mut self) {
69        unsafe {
70            drop(CString::from_raw(self.message));
71        }
72    }
73}
74
75/// Free a log.
76///
77/// # Safety
78/// The argument must be a non-null PkgcraftLog pointer or NULL.
79#[no_mangle]
80pub unsafe extern "C" fn pkgcraft_log_free(l: *mut PkgcraftLog) {
81    if !l.is_null() {
82        unsafe { drop(Box::from_raw(l)) };
83    }
84}
85
86type LogCallback = extern "C" fn(*mut PkgcraftLog);
87
88pub struct PkgcraftLayer {
89    log_cb: LogCallback,
90}
91
92impl PkgcraftLayer {
93    fn new(log_cb: LogCallback) -> Self {
94        Self { log_cb }
95    }
96}
97
98struct MessageVisitor<'a> {
99    message: &'a mut String,
100}
101
102impl<'a> Visit for MessageVisitor<'a> {
103    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
104        if field.name() == "message" {
105            write!(self.message, "{value:?}").unwrap();
106        }
107    }
108}
109
110impl<S> Layer<S> for PkgcraftLayer
111where
112    S: Subscriber,
113{
114    fn on_event(&self, event: &Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
115        (self.log_cb)(Box::into_raw(Box::new(event.into())));
116    }
117}
118
119thread_local! {
120    static SUBSCRIBER: RefCell<Option<DefaultGuard>> = const { RefCell::new(None) };
121}
122
123/// Enable pkgcraft logging support.
124#[no_mangle]
125pub extern "C" fn pkgcraft_logging_enable(cb: LogCallback, level: LogLevel) {
126    let level_filter: LevelFilter = level.into();
127    let filter = EnvFilter::builder()
128        .with_default_directive(level_filter.into())
129        .from_env_lossy();
130
131    let subscriber = Registry::default()
132        .with(filter)
133        .with(PkgcraftLayer::new(cb));
134
135    // replace the current thread's subscriber
136    SUBSCRIBER.with(|prev| *prev.borrow_mut() = None);
137    let guard = tracing::subscriber::set_default(subscriber);
138    SUBSCRIBER.with(|prev| *prev.borrow_mut() = Some(guard));
139}
140
141/// Replay a given PkgcraftLog object for test purposes.
142///
143/// # Safety
144/// The argument must be a non-null PkgcraftLog pointer.
145#[no_mangle]
146pub unsafe extern "C" fn pkgcraft_log_test(msg: *const c_char, level: LogLevel) {
147    let message = try_str_from_ptr!(msg);
148    use LogLevel::*;
149    match level {
150        Off => (),
151        Trace => tracing::trace!("{message}"),
152        Debug => tracing::debug!("{message}"),
153        Info => tracing::info!("{message}"),
154        Warn => tracing::warn!("{message}"),
155        Error => tracing::error!("{message}"),
156    }
157}