Skip to main content

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