Skip to main content

yang3/
logging.rs

1use std::borrow::Cow;
2use std::ffi::CStr;
3use std::os::raw::c_char;
4use std::sync::OnceLock;
5
6use crate::ffi;
7
8static LOG_CALLBACK: OnceLock<Box<dyn LogCallback>> = OnceLock::new();
9
10/// A custom logger to pass to libyang.
11pub trait LogCallback: Send + Sync + 'static {
12    fn log<'a>(
13        &'a self,
14        level: ffi::LY_LOG_LEVEL::Type,
15        msg: Option<Cow<'a, str>>,
16        data_path: Option<Cow<'a, str>>,
17        schema_path: Option<Cow<'a, str>>,
18        line: u64,
19    );
20}
21
22/// Set the log level to [`ffi::LY_LOG_LEVEL::LY_LLDBG`]
23pub(crate) fn set_log_level_trace() {
24    unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLDBG) };
25}
26
27/// Set the log level to [`ffi::LY_LOG_LEVEL::LY_LLVRB`]
28pub(crate) fn set_log_level_debug() {
29    unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLVRB) };
30}
31
32/// Set the log level to [`ffi::LY_LOG_LEVEL::LY_LLWRN`]
33pub(crate) fn set_log_level_warn() {
34    unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLWRN) };
35}
36
37/// Set the log level to [`ffi::LY_LOG_LEVEL::LY_LLERR`]
38pub(crate) fn set_log_level_error() {
39    unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLERR) };
40}
41
42/// An error returned when the logging callback has already been initialized.
43#[derive(Debug)]
44pub struct LoggingCallbackAlreadySet {
45    _private: (),
46}
47
48impl std::fmt::Display for LoggingCallbackAlreadySet {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "Logging callback already set")
51    }
52}
53
54impl std::error::Error for LoggingCallbackAlreadySet {}
55
56/// Initialize the logging callback.
57///
58/// The callback can only be initialized once.
59pub(crate) fn init_logger<C>(
60    callback: C,
61) -> Result<(), LoggingCallbackAlreadySet>
62where
63    C: LogCallback,
64{
65    unsafe { ffi::ly_log_options(ffi::LY_LOLOG | ffi::LY_LOSTORE_LAST) };
66    LOG_CALLBACK
67        .set(Box::new(callback))
68        .map_err(|_| LoggingCallbackAlreadySet { _private: () })?;
69    unsafe { ffi::ly_set_log_clb(Some(log_callback)) };
70    Ok(())
71}
72
73extern "C" fn log_callback(
74    level: ffi::LY_LOG_LEVEL::Type,
75    msg: *const c_char,
76    data_path: *const c_char,
77    schema_path: *const c_char,
78    line: u64,
79) {
80    let msg = if !msg.is_null() {
81        // SAFETY: we assume that the arguments passed to the callback
82        // are valid null terminated string valids for the entire
83        // execution of this function.
84        let cstr = unsafe { CStr::from_ptr(msg) };
85        Some(cstr.to_string_lossy())
86    } else {
87        None
88    };
89
90    let data_path = if !data_path.is_null() {
91        // SAFETY: we assume that the arguments passed to the callback
92        // are valid null terminated string valids for the entire
93        // execution of this function.
94        let cstr = unsafe { CStr::from_ptr(data_path) };
95        Some(cstr.to_string_lossy())
96    } else {
97        None
98    };
99
100    let schema_path = if !schema_path.is_null() {
101        // SAFETY: we assume that the arguments passed to the callback
102        // are valid null terminated string valids for the entire
103        // execution of this function.
104        let cstr = unsafe { CStr::from_ptr(schema_path) };
105        Some(cstr.to_string_lossy())
106    } else {
107        None
108    };
109
110    if let Some(cb) = LOG_CALLBACK.get() {
111        cb.log(level, msg, data_path, schema_path, line);
112    }
113}
114
115/// A logger that to log libyang message using the `log` crate.
116#[derive(Debug, Default)]
117pub struct DefaultLogger {
118    _private: (),
119}
120
121impl LogCallback for DefaultLogger {
122    fn log<'a>(
123        &'a self,
124        level: ffi::LY_LOG_LEVEL::Type,
125        msg: Option<Cow<'a, str>>,
126        data_path: Option<Cow<'a, str>>,
127        schema_path: Option<Cow<'a, str>>,
128        line: u64,
129    ) {
130        let level = match level {
131            ffi::LY_LOG_LEVEL::LY_LLERR => log::Level::Error,
132            ffi::LY_LOG_LEVEL::LY_LLWRN => log::Level::Warn,
133            ffi::LY_LOG_LEVEL::LY_LLVRB => log::Level::Info,
134            ffi::LY_LOG_LEVEL::LY_LLDBG => log::Level::Debug,
135            unknown => {
136                log::error!("Unexpected log level {unknown} from libyang3, logging as debug");
137                log::Level::Debug
138            }
139        };
140        let msg = msg.unwrap_or_else(|| Cow::from(""));
141        log::log! {
142            target: "libyang3",
143            level,
144            "schema_path={schema_path:?}, data_path={data_path:?}, line={line}, msg={msg}",
145        }
146    }
147}