libbpf_rs/print.rs
1use std::ffi::c_char;
2use std::ffi::c_int;
3use std::ffi::c_void;
4use std::io;
5use std::io::Write;
6use std::mem;
7use std::sync::Mutex;
8
9use crate::util::LazyLock;
10
11/// An enum representing the different supported print levels.
12#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
13#[repr(u32)]
14pub enum PrintLevel {
15 /// Print warnings and more severe messages.
16 Warn = libbpf_sys::LIBBPF_WARN,
17 /// Print general information and more severe messages.
18 Info = libbpf_sys::LIBBPF_INFO,
19 /// Print debug information and more severe messages.
20 Debug = libbpf_sys::LIBBPF_DEBUG,
21}
22
23impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
24 fn from(level: libbpf_sys::libbpf_print_level) -> Self {
25 match level {
26 libbpf_sys::LIBBPF_WARN => Self::Warn,
27 libbpf_sys::LIBBPF_INFO => Self::Info,
28 libbpf_sys::LIBBPF_DEBUG => Self::Debug,
29 // shouldn't happen, but anything unknown becomes the highest level
30 _ => Self::Warn,
31 }
32 }
33}
34
35/// The type of callback functions suitable for being provided to [`set_print`].
36pub type PrintCallback = fn(PrintLevel, String);
37
38/// Mimic the default print functionality of libbpf. This way if the user calls `get_print` when no
39/// previous callback had been set, with the intention of restoring it, everything will behave as
40/// expected.
41fn default_callback(_lvl: PrintLevel, msg: String) {
42 let _ = io::stderr().write(msg.as_bytes());
43}
44
45// While we can't say that set_print is thread-safe, because we shouldn't assume that of
46// libbpf_set_print, we should still make sure that things are sane on the rust side of things.
47// Therefore we are using a lock to keep the log level and the callback in sync.
48//
49// We don't do anything that can panic with the lock held, so we'll unconditionally unwrap() when
50// locking the mutex.
51//
52// Note that default print behavior ignores debug messages.
53static PRINT_CB: LazyLock<Mutex<Option<(PrintLevel, PrintCallback)>>> =
54 LazyLock::new(|| Mutex::new(Some((PrintLevel::Info, default_callback))));
55
56extern "C" fn outer_print_cb(
57 level: libbpf_sys::libbpf_print_level,
58 fmtstr: *const c_char,
59 // bindgen generated va_list type varies on different platforms, so just use void pointer
60 // instead. It's safe because this argument is always a pointer.
61 // The pointer of this function would be transmuted and passing to libbpf_set_print below.
62 // See <https://github.com/rust-lang/rust-bindgen/issues/2631>
63 va_list: *mut c_void,
64) -> c_int {
65 let level = level.into();
66 if let Some((min_level, func)) = { *PRINT_CB.lock().unwrap() } {
67 if level <= min_level {
68 let msg = match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
69 Ok(s) => s,
70 Err(e) => format!("Failed to parse libbpf output: {e}"),
71 };
72 func(level, msg);
73 }
74 }
75 0 // return value is ignored by libbpf
76}
77
78/// Set a callback to receive log messages from libbpf, instead of printing them to stderr.
79///
80/// # Arguments
81///
82/// * `callback` - Either a tuple `(min_level, function)` where `min_level` is the lowest priority
83/// log message to handle, or `None` to disable all printing.
84///
85/// This overrides (and is overridden by) [`ObjectBuilder::debug`][crate::ObjectBuilder::debug]
86///
87/// # Examples
88///
89/// To pass all messages to the `log` crate:
90///
91/// ```
92/// use libbpf_rs::{PrintLevel, set_print};
93///
94/// fn print_to_log(level: PrintLevel, msg: String) {
95/// match level {
96/// PrintLevel::Debug => log::debug!("{}", msg),
97/// PrintLevel::Info => log::info!("{}", msg),
98/// PrintLevel::Warn => log::warn!("{}", msg),
99/// }
100/// }
101///
102/// set_print(Some((PrintLevel::Debug, print_to_log)));
103/// ```
104///
105/// To disable printing completely:
106///
107/// ```
108/// use libbpf_rs::set_print;
109/// set_print(None);
110/// ```
111///
112/// To temporarliy suppress output:
113///
114/// ```
115/// use libbpf_rs::set_print;
116///
117/// let prev = set_print(None);
118/// // do things quietly
119/// set_print(prev);
120/// ```
121pub fn set_print(
122 mut callback: Option<(PrintLevel, PrintCallback)>,
123) -> Option<(PrintLevel, PrintCallback)> {
124 // # Safety
125 // outer_print_cb has the same function signature as libbpf_print_fn_t
126 #[allow(clippy::missing_transmute_annotations)]
127 let real_cb: libbpf_sys::libbpf_print_fn_t =
128 unsafe { Some(mem::transmute(outer_print_cb as *const ())) };
129 let real_cb: libbpf_sys::libbpf_print_fn_t = callback.as_ref().and(real_cb);
130 mem::swap(&mut callback, &mut *PRINT_CB.lock().unwrap());
131 unsafe { libbpf_sys::libbpf_set_print(real_cb) };
132 callback
133}
134
135/// Return the current print callback and level.
136///
137/// # Examples
138///
139/// To temporarily suppress output:
140///
141/// ```
142/// use libbpf_rs::{get_print, set_print};
143///
144/// let prev = get_print();
145/// set_print(None);
146/// // do things quietly
147/// set_print(prev);
148/// ```
149pub fn get_print() -> Option<(PrintLevel, PrintCallback)> {
150 *PRINT_CB.lock().unwrap()
151}