implement load-time log level mask

reviewable/pr1335/r6
Tamir Duberstein 23 hours 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 {
ctx,
target,
level: level_expr,
level,
format_string,
formatting_args,
} = args;
@ -80,14 +80,14 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
Some(t) => quote! { #t },
None => quote! { module_path!() },
};
let level = match level {
Some(l) => l,
let level_expr = match level_expr {
Some(level_expr) => level_expr,
None => {
let l = level_expr.ok_or(Error::new(
let level_expr = level.ok_or(Error::new(
format_string.span(),
"missing `level` argument: try passing an `aya_log_ebpf::Level` value",
))?;
quote! { #l }
quote! { #level_expr }
}
};
@ -146,37 +146,41 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
let num_args = values.len();
let values_iter = values.iter();
let level = Ident::new("level", Span::mixed_site());
let buf = Ident::new("buf", Span::mixed_site());
let size = Ident::new("size", Span::mixed_site());
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))
})();
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() }) {
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 `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
- 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
[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 `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";
pub const LEVEL: &str = "AYA_LOG_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};
pub use aya_log_common::Level;
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: 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 aya::{Ebpf, programs::UProbe};
use aya::{Ebpf, EbpfLoader, programs::UProbe};
use aya_log::EbpfLogger;
use log::{Level, Log, Record};
@ -173,3 +173,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