implement load-time log level mask

reviewable/pr1335/r6
Tamir Duberstein 1 day ago
parent a0b63b8811
commit b36cbc3eb8
No known key found for this signature in database

@ -68,11 +68,11 @@ impl Parse for LogArgs {
} }
} }
pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStream> { pub(crate) fn log(args: LogArgs, level_expr: Option<TokenStream>) -> Result<TokenStream> {
let LogArgs { let LogArgs {
ctx, ctx,
target, target,
level: level_expr, level,
format_string, format_string,
formatting_args, formatting_args,
} = args; } = args;
@ -80,14 +80,14 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
Some(t) => quote! { #t }, Some(t) => quote! { #t },
None => quote! { module_path!() }, None => quote! { module_path!() },
}; };
let level = match level { let level_expr = match level_expr {
Some(l) => l, Some(level_expr) => level_expr,
None => { None => {
let l = level_expr.ok_or(Error::new( let level_expr = level.ok_or(Error::new(
format_string.span(), format_string.span(),
"missing `level` argument: try passing an `aya_log_ebpf::Level` value", "missing `level` argument: try passing an `aya_log_ebpf::Level` value",
))?; ))?;
quote! { #l } quote! { #level_expr }
} }
}; };
@ -146,11 +146,14 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
let num_args = values.len(); let num_args = values.len();
let values_iter = values.iter(); let values_iter = values.iter();
let level = Ident::new("level", Span::mixed_site());
let buf = Ident::new("buf", Span::mixed_site()); let buf = Ident::new("buf", Span::mixed_site());
let size = Ident::new("size", Span::mixed_site()); let size = Ident::new("size", Span::mixed_site());
let len = Ident::new("len", Span::mixed_site()); let len = Ident::new("len", Span::mixed_site());
let record = Ident::new("record", Span::mixed_site()); let record = Ident::new("record", Span::mixed_site());
Ok(quote! { Ok(quote! {
let #level = #level_expr;
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() }) { match ::aya_log_ebpf::macro_support::AYA_LOG_BUF.get_ptr_mut(0).and_then(|ptr| unsafe { ptr.as_mut() }) {
None => {}, None => {},
Some(::aya_log_ebpf::macro_support::LogBuf { buf: #buf }) => { Some(::aya_log_ebpf::macro_support::LogBuf { buf: #buf }) => {
@ -179,6 +182,7 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
})(); })();
} }
} }
}
}) })
} }

@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### New Features
- Add eBPF-side log level `AYA_LOG_LEVEL` 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 ### Breaking Changes
- The implementation is now backed by a ring buffer rather than a perf event array. This should - The implementation is now backed by a ring buffer rather than a perf event array. This should

@ -69,3 +69,33 @@ fn try_xdp_firewall(ctx: XdpContext) -> Result<u32, ()> {
[aya]: https://github.com/aya-rs/aya [aya]: https://github.com/aya-rs/aya
[log]: https://docs.rs/log [log]: https://docs.rs/log
[env_logger]: https://docs.rs/env_logger [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 `AYA_LOG_LEVEL` inside the
eBPF object allowing you to selectively enable levels before the program is
loaded.
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 level 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,14 @@ use std::{
const MAP_NAME: &str = "AYA_LOGS"; const MAP_NAME: &str = "AYA_LOGS";
pub const LEVEL: &str = "AYA_LOG_LEVEL";
use aya::{ use aya::{
Ebpf, Pod, Ebpf, Pod,
maps::{Map, MapData, MapError, MapInfo, RingBuf}, maps::{Map, MapData, MapError, MapInfo, RingBuf},
programs::{ProgramError, loaded_programs}, programs::{ProgramError, loaded_programs},
}; };
use aya_log_common::{Argument, DisplayHint, LOG_FIELDS, Level, LogValueLength, RecordField}; pub use aya_log_common::Level;
use aya_log_common::{Argument, DisplayHint, LOG_FIELDS, LogValueLength, RecordField};
use log::{Log, Record, error}; use log::{Log, Record, error};
use thiserror::Error; use thiserror::Error;

@ -34,4 +34,18 @@ pub mod macro_support {
// test/integration-test/Cargo.toml. // test/integration-test/Cargo.toml.
#[cfg_attr(target_arch = "bpf", map)] #[cfg_attr(target_arch = "bpf", map)]
pub static AYA_LOGS: RingBuf = RingBuf::with_byte_size((LOG_BUF_CAPACITY as u32) << 4, 0); 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: u8 = 0xff;
/// 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
}
} }

@ -1,6 +1,6 @@
use std::{borrow::Cow, sync::Mutex}; use std::{borrow::Cow, sync::Mutex};
use aya::{Ebpf, programs::UProbe}; use aya::{Ebpf, EbpfLoader, programs::UProbe};
use aya_log::EbpfLogger; use aya_log::EbpfLogger;
use log::{Level, Log, Record}; use log::{Level, Log, Record};
@ -173,3 +173,54 @@ fn log() {
assert_eq!(records.next(), None); 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 mod aya_log
pub use aya_log::Level
pub enum aya_log::Error pub enum aya_log::Error
pub aya_log::Error::MapError(aya::maps::MapError) pub aya_log::Error::MapError(aya::maps::MapError)
pub aya_log::Error::MapNotFound 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 pub fn aya_log::UpperMacFormatter::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_log::UpperMacFormatter impl<T> core::convert::From<T> for aya_log::UpperMacFormatter
pub fn aya_log::UpperMacFormatter::from(t: T) -> T pub fn aya_log::UpperMacFormatter::from(t: T) -> T
pub const aya_log::LEVEL: &str
pub trait aya_log::Formatter<T> pub trait aya_log::Formatter<T>
pub fn aya_log::Formatter::format(v: T) -> alloc::string::String pub fn aya_log::Formatter::format(v: T) -> alloc::string::String
impl aya_log::Formatter<&[u8]> for aya_log::LowerHexBytesFormatter impl aya_log::Formatter<&[u8]> for aya_log::LowerHexBytesFormatter

Loading…
Cancel
Save