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
10pub 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
22pub(crate) fn set_log_level_trace() {
24 unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLDBG) };
25}
26
27pub(crate) fn set_log_level_debug() {
29 unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLVRB) };
30}
31
32pub(crate) fn set_log_level_warn() {
34 unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLWRN) };
35}
36
37pub(crate) fn set_log_level_error() {
39 unsafe { ffi::ly_log_level(ffi::LY_LOG_LEVEL::LY_LLERR) };
40}
41
42#[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
56pub(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 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 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 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#[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}