blob: b71c81d1e21424f48d1608f8bd65c6a10aa05cc6 [file] [log] [blame] [edit]
use std::ffi::c_char;
use std::ffi::c_int;
use std::ffi::c_void;
use std::io;
use std::io::Write;
use std::mem;
use std::sync::Mutex;
use crate::util::LazyLock;
/// An enum representing the different supported print levels.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[repr(u32)]
pub enum PrintLevel {
/// Print warnings and more severe messages.
Warn = libbpf_sys::LIBBPF_WARN,
/// Print general information and more severe messages.
Info = libbpf_sys::LIBBPF_INFO,
/// Print debug information and more severe messages.
Debug = libbpf_sys::LIBBPF_DEBUG,
}
impl From<libbpf_sys::libbpf_print_level> for PrintLevel {
fn from(level: libbpf_sys::libbpf_print_level) -> Self {
match level {
libbpf_sys::LIBBPF_WARN => Self::Warn,
libbpf_sys::LIBBPF_INFO => Self::Info,
libbpf_sys::LIBBPF_DEBUG => Self::Debug,
// shouldn't happen, but anything unknown becomes the highest level
_ => Self::Warn,
}
}
}
/// The type of callback functions suitable for being provided to [`set_print`].
pub type PrintCallback = fn(PrintLevel, String);
/// Mimic the default print functionality of libbpf. This way if the user calls `get_print` when no
/// previous callback had been set, with the intention of restoring it, everything will behave as
/// expected.
fn default_callback(_lvl: PrintLevel, msg: String) {
let _ = io::stderr().write(msg.as_bytes());
}
// While we can't say that set_print is thread-safe, because we shouldn't assume that of
// libbpf_set_print, we should still make sure that things are sane on the rust side of things.
// Therefore we are using a lock to keep the log level and the callback in sync.
//
// We don't do anything that can panic with the lock held, so we'll unconditionally unwrap() when
// locking the mutex.
//
// Note that default print behavior ignores debug messages.
static PRINT_CB: LazyLock<Mutex<Option<(PrintLevel, PrintCallback)>>> =
LazyLock::new(|| Mutex::new(Some((PrintLevel::Info, default_callback))));
extern "C" fn outer_print_cb(
level: libbpf_sys::libbpf_print_level,
fmtstr: *const c_char,
// bindgen generated va_list type varies on different platforms, so just use void pointer
// instead. It's safe because this argument is always a pointer.
// The pointer of this function would be transmuted and passing to libbpf_set_print below.
// See <https://github.com/rust-lang/rust-bindgen/issues/2631>
va_list: *mut c_void,
) -> c_int {
let level = level.into();
if let Some((min_level, func)) = { *PRINT_CB.lock().unwrap() } {
if level <= min_level {
let msg = match unsafe { vsprintf::vsprintf(fmtstr, va_list) } {
Ok(s) => s,
Err(e) => format!("Failed to parse libbpf output: {e}"),
};
func(level, msg);
}
}
0 // return value is ignored by libbpf
}
/// Set a callback to receive log messages from libbpf, instead of printing them to stderr.
///
/// # Arguments
///
/// * `callback` - Either a tuple `(min_level, function)` where `min_level` is the lowest priority
/// log message to handle, or `None` to disable all printing.
///
/// This overrides (and is overridden by) [`ObjectBuilder::debug`][crate::ObjectBuilder::debug]
///
/// # Examples
///
/// To pass all messages to the `log` crate:
///
/// ```
/// use libbpf_rs::{PrintLevel, set_print};
///
/// fn print_to_log(level: PrintLevel, msg: String) {
/// match level {
/// PrintLevel::Debug => log::debug!("{}", msg),
/// PrintLevel::Info => log::info!("{}", msg),
/// PrintLevel::Warn => log::warn!("{}", msg),
/// }
/// }
///
/// set_print(Some((PrintLevel::Debug, print_to_log)));
/// ```
///
/// To disable printing completely:
///
/// ```
/// use libbpf_rs::set_print;
/// set_print(None);
/// ```
///
/// To temporarliy suppress output:
///
/// ```
/// use libbpf_rs::set_print;
///
/// let prev = set_print(None);
/// // do things quietly
/// set_print(prev);
/// ```
pub fn set_print(
mut callback: Option<(PrintLevel, PrintCallback)>,
) -> Option<(PrintLevel, PrintCallback)> {
// # Safety
// outer_print_cb has the same function signature as libbpf_print_fn_t
#[allow(clippy::missing_transmute_annotations)]
let real_cb: libbpf_sys::libbpf_print_fn_t =
unsafe { Some(mem::transmute(outer_print_cb as *const ())) };
let real_cb: libbpf_sys::libbpf_print_fn_t = callback.as_ref().and(real_cb);
mem::swap(&mut callback, &mut *PRINT_CB.lock().unwrap());
unsafe { libbpf_sys::libbpf_set_print(real_cb) };
callback
}
/// Return the current print callback and level.
///
/// # Examples
///
/// To temporarily suppress output:
///
/// ```
/// use libbpf_rs::{get_print, set_print};
///
/// let prev = get_print();
/// set_print(None);
/// // do things quietly
/// set_print(prev);
/// ```
pub fn get_print() -> Option<(PrintLevel, PrintCallback)> {
*PRINT_CB.lock().unwrap()
}