implement load-time log level mask

reviewable/pr1335/r1
Tamir Duberstein 1 week ago
parent a0b63b8811
commit 18a3321e60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -151,32 +151,34 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
let len = Ident::new("len", Span::mixed_site());
let record = Ident::new("record", Span::mixed_site());
Ok(quote! {
match ::aya_log_ebpf::macro_support::AYA_LOG_BUF.get_ptr_mut(0).and_then(|ptr| unsafe { ptr.as_mut() }) {
None => {},
Some(::aya_log_ebpf::macro_support::LogBuf { buf: #buf }) => {
// Silence unused variable warning; we may need ctx in the future.
let _ = #ctx;
let _: Option<()> = (|| {
let #size = ::aya_log_ebpf::macro_support::write_record_header(
#buf,
#target,
#level,
module_path!(),
file!(),
line!(),
#num_args,
)?;
let mut #size = #size.get();
#(
{
let #buf = #buf.get_mut(#size..)?;
let #len = ::aya_log_ebpf::macro_support::WriteToBuf::write(#values_iter, #buf)?;
#size += #len.get();
}
)*
let #record = #buf.get(..#size)?;
Result::<_, i64>::ok(::aya_log_ebpf::macro_support::AYA_LOGS.output(#record, 0))
})();
if ::aya_log_ebpf::macro_support::level_enabled(#level) {
match ::aya_log_ebpf::macro_support::AYA_LOG_BUF.get_ptr_mut(0).and_then(|ptr| unsafe { ptr.as_mut() }) {
None => {},
Some(::aya_log_ebpf::macro_support::LogBuf { buf: #buf }) => {
// Silence unused variable warning; we may need ctx in the future.
let _ = #ctx;
let _: Option<()> = (|| {
let #size = ::aya_log_ebpf::macro_support::write_record_header(
#buf,
#target,
#level,
module_path!(),
file!(),
line!(),
#num_args,
)?;
let mut #size = #size.get();
#(
{
let #buf = #buf.get_mut(#size..)?;
let #len = ::aya_log_ebpf::macro_support::WriteToBuf::write(#values_iter, #buf)?;
#size += #len.get();
}
)*
let #record = #buf.get(..#size)?;
Result::<_, i64>::ok(::aya_log_ebpf::macro_support::AYA_LOGS.output(#record, 0))
})();
}
}
}
})

@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### New Features
- Add eBPF-side log level mask `AYA_LOG_LEVEL_MASK` and loader API `EbpfLoader::set_log_level_mask()`
allowing selective disabling of log levels at load-time. Disabled levels are
eliminated by the verifier, reducing instruction count and avoiding program
size limits when extensive logging is present.
### Breaking Changes
- The implementation is now backed by a ring buffer rather than a perf event array. This should

@ -69,3 +69,37 @@ fn try_xdp_firewall(ctx: XdpContext) -> Result<u32, ()> {
[aya]: https://github.com/aya-rs/aya
[log]: https://docs.rs/log
[env_logger]: https://docs.rs/env_logger
## Disabling log levels at load-time
eBPF instruction budgets are tight. Even if a log statement never executes at
runtime, the verifier must still evaluate its instructions unless it can prove
they're unreachable. `aya-log` now exposes a global bitmask `AYA_LOG_LEVEL_MASK`
inside the eBPF object allowing you to selectively enable levels before the
program is loaded.
Bits (least-significant first) map to levels: Error=bit0, Warn=bit1, Info=bit2,
Debug=bit3, Trace=bit4.
By default all bits are set (all logging enabled). To disable all logging:
```rust
use aya::EbpfLoader;
let mut bpf = EbpfLoader::new()
.set_log_level_mask(0) // disable all levels
.load_file("prog.bpf.o")?;
# Ok::<(), aya::EbpfError>(())
```
Enable only Error and Warn:
```rust
let mut bpf = EbpfLoader::new()
.set_log_level_mask(0b11)
.load_file("prog.bpf.o")?;
```
Because the mask is placed in global read-only data, the verifier sees the
disabled branch as unreachable and prunes the logging instructions, reducing
overall instruction count and avoiding potential instruction limit issues.

@ -341,6 +341,21 @@ impl<'a> EbpfLoader<'a> {
self
}
/// Sets the aya-log eBPF log level mask before loading programs.
///
/// This is a convenience wrapper around [`EbpfLoader::set_global`] that patches the
/// `AYA_LOG_LEVEL_MASK` symbol exposed by `aya-log-ebpf`.
///
/// Each bit enables a level where bit0=Error, bit1=Warn, bit2=Info, bit3=Debug, bit4=Trace.
/// For example to enable only Error and Warn pass `0b0000_0011` (3). To disable all logging
/// pass 0.
///
/// If the symbol does not exist in the object (e.g., logging not linked) no error is raised.
pub fn set_log_level_mask(&mut self, mask: u32) -> &mut Self {
// Best-effort: don't require symbol to exist.
self.set_global("AYA_LOG_LEVEL_MASK", &mask, false)
}
/// Loads eBPF bytecode from a file.
///
/// # Examples

@ -9,6 +9,8 @@ pub mod macro_support {
use aya_ebpf::macros::map;
use aya_ebpf::maps::{PerCpuArray, RingBuf};
use aya_log_common::LogValueLength;
#[cfg(target_arch = "bpf")]
use core::ptr::read_volatile;
pub use aya_log_common::{
DefaultFormatter, DisplayHint, IpFormatter, Level, LowerHexFormatter, LowerMacFormatter,
UpperHexFormatter, UpperMacFormatter, WriteToBuf, write_record_header,
@ -34,4 +36,21 @@ pub mod macro_support {
// test/integration-test/Cargo.toml.
#[cfg_attr(target_arch = "bpf", map)]
pub static AYA_LOGS: RingBuf = RingBuf::with_byte_size((LOG_BUF_CAPACITY as u32) << 4, 0);
/// Global log level mask controlling which log statements are active.
/// Bits correspond to variants of `Level` (1-based discriminant) shifted into a mask.
/// By default all bits set (enable all levels). Userspace may patch this symbol before load
/// via `EbpfLoader::set_log_level_mask` (to be implemented) using global data patching.
/// When a bit is unset, verifier will see the guarded block as unreachable and prune it.
#[no_mangle]
pub static mut AYA_LOG_LEVEL_MASK: u32 = 0xFFFF_FFFF;
/// Returns `true` if the provided level is enabled according to `AYA_LOG_LEVEL_MASK`.
#[inline(always)]
pub fn level_enabled(lvl: Level) -> bool {
// SAFETY: Mask lives in .rodata for BPF (declared mutable for patching), we only read it.
let mask = unsafe { read_volatile(&AYA_LOG_LEVEL_MASK) };
let bit: u32 = 1u32 << (lvl as u32 - 1);
(mask & bit) != 0
}
}

@ -2,6 +2,7 @@ use std::{borrow::Cow, sync::Mutex};
use aya::{Ebpf, programs::UProbe};
use aya_log::EbpfLogger;
use aya::EbpfLoader;
use log::{Level, Log, Record};
#[unsafe(no_mangle)]
@ -173,3 +174,48 @@ fn log() {
assert_eq!(records.next(), None);
}
#[test_log::test]
fn log_level_mask_only_error_warn() {
// Enable only Error (bit0) and Warn (bit1)
let mut bpf = EbpfLoader::new()
.set_log_level_mask(0b11)
.load(crate::LOG)
.unwrap();
let mut captured_logs = Vec::new();
let logger = TestingLogger {
log: Mutex::new(|record: &Record| {
captured_logs.push(CapturedLog {
body: format!("{}", record.args()).into(),
level: record.level(),
target: record.target().to_string().into(),
});
}),
};
let mut logger = EbpfLogger::init_with_logger(&mut bpf, &logger).unwrap();
let prog: &mut UProbe = bpf.program_mut("test_log").unwrap().try_into().unwrap();
prog.load().unwrap();
prog.attach("trigger_ebpf_program", "/proc/self/exe", None, None)
.unwrap();
trigger_ebpf_program();
logger.flush();
// We expect only the Error and Warn records to be present, in program order.
let expected: Vec<CapturedLog<'_>> = vec![
CapturedLog {
body: "69, 420, wao, 77616f".into(),
level: Level::Error,
target: "log".into(),
},
CapturedLog {
body: "hex lc: 2f, hex uc: 2F".into(),
level: Level::Warn,
target: "log".into(),
},
];
assert_eq!(captured_logs, expected, "unexpected log records when mask filters levels");
}

Loading…
Cancel
Save