implement load-time log level mask

reviewable/pr1335/r3
Tamir Duberstein 1 week ago
parent a0b63b8811
commit 6fc2233b98
No known key found for this signature in database

@ -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,36 @@ 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
let mut bpf = aya::EbpfLoader::new()
.set_global(aya_log::LEVEL, &0, false /* must_exist */)
.load_file("prog.bpf.o")?;
# Ok::<(), aya::EbpfError>(())
```
Enable only Error and Warn:
```rust
let level = aya_log::Level::Warn as u8;
let mut bpf = EbpfLoader::new()
.set_global(aya_log::LEVEL, &level, false /* must_exist */)
.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.

@ -67,12 +67,15 @@ use std::{
const MAP_NAME: &str = "AYA_LOGS";
pub const LEVEL: &str = "AYA_LOG_LEVEL";
pub use aya_log_common::Level;
use aya::{
Ebpf, Pod,
maps::{Map, MapData, MapError, MapInfo, RingBuf},
programs::{ProgramError, loaded_programs},
};
use aya_log_common::{Argument, DisplayHint, LOG_FIELDS, Level, LogValueLength, RecordField};
use aya_log_common::{Argument, DisplayHint, LOG_FIELDS, LogValueLength, RecordField};
use log::{Log, Record, error};
use thiserror::Error;

@ -34,4 +34,18 @@ 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 controlling which log statements are active.
///
/// Userspace may patch this symbol before load via `EbpfLoader::set_global`.
#[unsafe(no_mangle)]
pub static mut AYA_LOG_LEVEL: aya_log_common::Level = aya_log_common::Level::Trace;
/// Returns `true` if the provided level is enabled according to [`AYA_LOG_LEVEL`].
#[inline(always)]
pub fn level_enabled(level: Level) -> bool {
#[expect(static_mut_refs)]
let current_level = unsafe { core::ptr::read_volatile(&AYA_LOG_LEVEL) };
level as u8 <= current_level as u8
}
}

@ -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,54 @@ fn log() {
assert_eq!(records.next(), None);
}
#[test_log::test]
fn log_level_only_error_warn() {
let level = aya_log::Level::Warn as u8;
let mut bpf = EbpfLoader::new()
.set_global(aya_log::LEVEL, &level, true /* must_exist */)
.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();
let mut records = captured_logs.iter();
assert_eq!(
records.next(),
Some(&CapturedLog {
body: "69, 420, wao, 77616f".into(),
level: Level::Error,
target: "log".into(),
})
);
assert_eq!(
records.next(),
Some(&CapturedLog {
body: "hex lc: 2f, hex uc: 2F".into(),
level: Level::Warn,
target: "log".into(),
})
);
assert_eq!(records.next(), None);
}

@ -1,4 +1,5 @@
pub mod aya_log
pub use aya_log::Level
pub enum aya_log::Error
pub aya_log::Error::MapError(aya::maps::MapError)
pub aya_log::Error::MapNotFound
@ -297,6 +298,7 @@ impl<T> core::borrow::BorrowMut<T> for aya_log::UpperMacFormatter where T: ?core
pub fn aya_log::UpperMacFormatter::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_log::UpperMacFormatter
pub fn aya_log::UpperMacFormatter::from(t: T) -> T
pub const aya_log::LEVEL: &str
pub trait aya_log::Formatter<T>
pub fn aya_log::Formatter::format(v: T) -> alloc::string::String
impl aya_log::Formatter<&[u8]> for aya_log::LowerHexBytesFormatter

Loading…
Cancel
Save