#[doc(hidden)]
pub mod macros_internal {
pub use crate::encode::Encode;
pub use crate::metadata::LogMetadata;
pub use crate::record::RecordHeader;
#[must_use]
pub fn timestamp_ns() -> u64 {
use std::time::SystemTime;
#[expect(
clippy::cast_possible_truncation,
reason = "timestamps past year 2554 are not a supported use case"
)]
let ns = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
ns
}
pub fn log_write(total_size: usize, f: impl FnOnce(*mut u8)) {
crate::frontend::with_producer(|p| {
if p.write(total_size, |buf| f(buf.as_mut_ptr())).is_err() {
crate::lifecycle::get_backend().increment_dropped_records();
}
});
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __count_args {
() => { 0u8 };
($head:expr $(, $tail:expr)*) => { 1u8 + $crate::__count_args!($($tail),*) };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __log {
($logger:expr, $level:expr, $fmt:expr $(, $arg:expr)*) => {{
let logger_ref: &$crate::Logger = &*$logger;
if $level >= logger_ref.level() {
static METADATA: $crate::macros_internal::LogMetadata =
$crate::macros_internal::LogMetadata {
level: $level,
fmt_str: $fmt,
file: file!(),
line: line!(),
module_path: module_path!(),
arg_count: $crate::__count_args!($($arg),*),
};
#[expect(
clippy::cast_possible_truncation,
reason = "log records larger than 4 GiB are not supported"
)]
let encoded_args_size: u32 =
(0usize $(+ $crate::macros_internal::Encode::encoded_size(&$arg))*)
as u32;
let total_size =
$crate::macros_internal::RecordHeader::SIZE + encoded_args_size as usize;
$crate::macros_internal::log_write(total_size, |buf_ptr| {
unsafe {
let header = $crate::macros_internal::RecordHeader::new(
$crate::macros_internal::timestamp_ns(),
core::ptr::from_ref(&METADATA) as usize,
core::ptr::from_ref(logger_ref) as usize,
encoded_args_size,
);
header.write_to(buf_ptr);
#[allow(unused_mut)]
let mut offset = $crate::macros_internal::RecordHeader::SIZE;
$(
let arg_size =
$crate::macros_internal::Encode::encoded_size(&$arg);
$crate::macros_internal::Encode::encode_to(
&$arg,
buf_ptr.add(offset),
);
offset += arg_size;
)*
let _ = offset;
}
});
}
}};
}
#[macro_export]
macro_rules! log_trace {
($logger:expr, $fmt:expr $(, $arg:expr)*) => {
$crate::__log!($logger, $crate::LogLevel::Trace, $fmt $(, $arg)*)
};
}
#[macro_export]
macro_rules! log_debug {
($logger:expr, $fmt:expr $(, $arg:expr)*) => {
$crate::__log!($logger, $crate::LogLevel::Debug, $fmt $(, $arg)*)
};
}
#[macro_export]
macro_rules! log_info {
($logger:expr, $fmt:expr $(, $arg:expr)*) => {
$crate::__log!($logger, $crate::LogLevel::Info, $fmt $(, $arg)*)
};
}
#[macro_export]
macro_rules! log_warn {
($logger:expr, $fmt:expr $(, $arg:expr)*) => {
$crate::__log!($logger, $crate::LogLevel::Warning, $fmt $(, $arg)*)
};
}
#[macro_export]
macro_rules! log_error {
($logger:expr, $fmt:expr $(, $arg:expr)*) => {
$crate::__log!($logger, $crate::LogLevel::Error, $fmt $(, $arg)*)
};
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use crate::backend::BackendOptions;
use crate::level::LogLevel;
use crate::lifecycle;
use crate::sink::Sink;
use crate::testutil::RecordingSink;
fn fast_options() -> BackendOptions {
BackendOptions {
idle_sleep: Duration::from_micros(10),
idle_yield_rounds: 0,
..BackendOptions::default()
}
}
#[test]
fn record_below_logger_level_is_not_emitted_and_skips_with_producer() {
let sink = Arc::new(RecordingSink::new(LogLevel::Trace));
let _guard = lifecycle::start(fast_options()).expect("start");
let backend = lifecycle::get_backend();
let logger = lifecycle::create_logger(
"macro_level_filter",
vec![Arc::clone(&sink) as Arc<dyn Sink>],
LogLevel::Warning,
)
.expect("create_logger");
let logger_arc = Arc::clone(&logger);
std::thread::spawn(move || {
log_info!(logger_arc, "below level, must be dropped");
})
.join()
.expect("spawned thread must not panic");
assert_eq!(
backend.consumer_count(),
0,
"level check must prevent with_producer from being called"
);
assert_eq!(
sink.record_count(),
0,
"no records must be emitted below the logger level"
);
}
}