diff --git a/Cargo.toml b/Cargo.toml index 61c3b7df..07909562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "aya", "aya-gen", "aya-log", "aya-log-common", "test/integration-test", "test/integration-test-macros", "xtask", + "aya", "aya-gen", "aya-log", "aya-log-common", "aya-log-parser", "test/integration-test", "test/integration-test-macros", "xtask", # macros "aya-bpf-macros", "aya-log-ebpf-macros", # ebpf crates diff --git a/aya-log-common/Cargo.toml b/aya-log-common/Cargo.toml index 5d89b340..9c55f8dd 100644 --- a/aya-log-common/Cargo.toml +++ b/aya-log-common/Cargo.toml @@ -15,6 +15,7 @@ userspace = [ "aya" ] [dependencies] aya = { path = "../aya", version = "0.11.0", optional=true } +num_enum = { version = "0.5", default-features = false } [lib] path = "src/lib.rs" diff --git a/aya-log-common/src/lib.rs b/aya-log-common/src/lib.rs index 9b9d6899..e1cc8f92 100644 --- a/aya-log-common/src/lib.rs +++ b/aya-log-common/src/lib.rs @@ -1,10 +1,12 @@ #![no_std] -use core::{cmp, mem, ptr}; +use core::{cmp, mem, ptr, slice}; + +use num_enum::IntoPrimitive; pub const LOG_BUF_CAPACITY: usize = 8192; -pub const LOG_FIELDS: usize = 7; +pub const LOG_FIELDS: usize = 6; #[repr(usize)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] @@ -40,12 +42,13 @@ pub enum RecordField { File, Line, NumArgs, - Log, } #[repr(usize)] #[derive(Copy, Clone, Debug)] -pub enum ArgType { +pub enum Argument { + DisplayHint, + I8, I16, I32, @@ -61,15 +64,35 @@ pub enum ArgType { F32, F64, + ArrU8Len16, + ArrU16Len8, + Str, } +/// All display hints +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, IntoPrimitive)] +pub enum DisplayHint { + /// Default string representation. + Default = 1, + /// `:x` + LowerHex, + /// `:X` + UpperHex, + /// `:ipv4` + Ipv4, + /// `:ipv6` + Ipv6, +} + #[cfg(feature = "userspace")] mod userspace { use super::*; unsafe impl aya::Pod for RecordField {} - unsafe impl aya::Pod for ArgType {} + unsafe impl aya::Pod for Argument {} + unsafe impl aya::Pod for DisplayHint {} } struct TagLenValue<'a, T> { @@ -120,30 +143,51 @@ macro_rules! impl_write_to_buf { ($type:ident, $arg_type:expr) => { impl WriteToBuf for $type { fn write(&self, buf: &mut [u8]) -> Result { - TagLenValue::::new($arg_type, &self.to_ne_bytes()).write(buf) + TagLenValue::::new($arg_type, &self.to_ne_bytes()).write(buf) } } }; } -impl_write_to_buf!(i8, ArgType::I8); -impl_write_to_buf!(i16, ArgType::I16); -impl_write_to_buf!(i32, ArgType::I32); -impl_write_to_buf!(i64, ArgType::I64); -impl_write_to_buf!(isize, ArgType::Isize); +impl_write_to_buf!(i8, Argument::I8); +impl_write_to_buf!(i16, Argument::I16); +impl_write_to_buf!(i32, Argument::I32); +impl_write_to_buf!(i64, Argument::I64); +impl_write_to_buf!(isize, Argument::Isize); -impl_write_to_buf!(u8, ArgType::U8); -impl_write_to_buf!(u16, ArgType::U16); -impl_write_to_buf!(u32, ArgType::U32); -impl_write_to_buf!(u64, ArgType::U64); -impl_write_to_buf!(usize, ArgType::Usize); +impl_write_to_buf!(u8, Argument::U8); +impl_write_to_buf!(u16, Argument::U16); +impl_write_to_buf!(u32, Argument::U32); +impl_write_to_buf!(u64, Argument::U64); +impl_write_to_buf!(usize, Argument::Usize); -impl_write_to_buf!(f32, ArgType::F32); -impl_write_to_buf!(f64, ArgType::F64); +impl_write_to_buf!(f32, Argument::F32); +impl_write_to_buf!(f64, Argument::F64); + +impl WriteToBuf for [u8; 16] { + fn write(&self, buf: &mut [u8]) -> Result { + TagLenValue::::new(Argument::ArrU8Len16, self).write(buf) + } +} + +impl WriteToBuf for [u16; 8] { + fn write(&self, buf: &mut [u8]) -> Result { + let ptr = self.as_ptr().cast::(); + let bytes = unsafe { slice::from_raw_parts(ptr, 16) }; + TagLenValue::::new(Argument::ArrU16Len8, bytes).write(buf) + } +} impl WriteToBuf for str { fn write(&self, buf: &mut [u8]) -> Result { - TagLenValue::::new(ArgType::Str, self.as_bytes()).write(buf) + TagLenValue::::new(Argument::Str, self.as_bytes()).write(buf) + } +} + +impl WriteToBuf for DisplayHint { + fn write(&self, buf: &mut [u8]) -> Result { + let v: u8 = (*self).into(); + TagLenValue::::new(Argument::DisplayHint, &v.to_ne_bytes()).write(buf) } } @@ -173,9 +217,3 @@ pub fn write_record_header( Ok(size) } - -#[allow(clippy::result_unit_err)] -#[doc(hidden)] -pub fn write_record_message(buf: &mut [u8], msg: &str) -> Result { - TagLenValue::::new(RecordField::Log, msg.as_bytes()).write(buf) -} diff --git a/aya-log-ebpf-macros/Cargo.toml b/aya-log-ebpf-macros/Cargo.toml index d87af99a..5396ed16 100644 --- a/aya-log-ebpf-macros/Cargo.toml +++ b/aya-log-ebpf-macros/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +aya-log-common = { path = "../aya-log-common" } +aya-log-parser = { path = "../aya-log-parser" } proc-macro2 = "1.0" quote = "1.0" syn = "1.0" diff --git a/aya-log-ebpf-macros/src/expand.rs b/aya-log-ebpf-macros/src/expand.rs index b1279b12..f1fb0314 100644 --- a/aya-log-ebpf-macros/src/expand.rs +++ b/aya-log-ebpf-macros/src/expand.rs @@ -2,10 +2,14 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ parse::{Parse, ParseStream}, + parse_str, punctuated::Punctuated, Error, Expr, LitStr, Result, Token, }; +use aya_log_common::DisplayHint; +use aya_log_parser::{parse, Fragment}; + pub(crate) struct LogArgs { pub(crate) ctx: Expr, pub(crate) target: Option, @@ -66,6 +70,20 @@ impl Parse for LogArgs { } } +fn string_to_expr(s: String) -> Result { + parse_str(&format!("\"{}\"", s)) +} + +fn hint_to_expr(hint: DisplayHint) -> Result { + match hint { + DisplayHint::Default => parse_str("::aya_log_ebpf::macro_support::DisplayHint::Default"), + DisplayHint::LowerHex => parse_str("::aya_log_ebpf::macro_support::DisplayHint::LowerHex"), + DisplayHint::UpperHex => parse_str("::aya_log_ebpf::macro_support::DisplayHint::UpperHex"), + DisplayHint::Ipv4 => parse_str("::aya_log_ebpf::macro_support::DisplayHint::IPv4"), + DisplayHint::Ipv6 => parse_str("::aya_log_ebpf::macro_support::DisplayHint::IPv6"), + } +} + pub(crate) fn log(args: LogArgs, level: Option) -> Result { let ctx = args.ctx; let target = match args.target { @@ -84,47 +102,36 @@ pub(crate) fn log(args: LogArgs, level: Option) -> Result { - let formatting_exprs = formatting_args.iter(); - let num_args = formatting_exprs.len(); - - let write_args = quote! {{ - use ::aya_log_ebpf::WriteToBuf; - Ok::<_, ()>(record_len) #( .and_then(|record_len| { - if record_len >= buf.buf.len() { - return Err(()); - } - { #formatting_exprs }.write(&mut buf.buf[record_len..]).map(|len| record_len + len) - }) )* - }}; - - (num_args, write_args) - } - None => (0, quote! {}), - }; - - // The way of writing to the perf buffer is different depending on whether - // we have variadic arguments or not. - let write_to_perf_buffer = if num_args > 0 { - // Writing with variadic arguments. - quote! { - if let Ok(record_len) = #write_args { - unsafe { ::aya_log_ebpf::AYA_LOGS.output( - #ctx, - &buf.buf[..record_len], 0 - )} + let format_string_val = format_string.value(); + let fragments = parse(&format_string_val).map_err(|e| { + Error::new( + format_string.span(), + format!("could not parse the format string: {}", e), + ) + })?; + + let mut arg_i = 0; + + let mut values = Vec::new(); + for fragment in fragments { + match fragment { + Fragment::Literal(s) => { + values.push(string_to_expr(s)?); + } + Fragment::Parameter(p) => { + let arg = match args.formatting_args { + Some(ref args) => args[arg_i].clone(), + None => return Err(Error::new(format_string.span(), "no arguments provided")), + }; + values.push(hint_to_expr(p.hint)?); + values.push(arg); + arg_i += 1; } } - } else { - // Writing with no variadic arguments. - quote! { - unsafe { ::aya_log_ebpf::AYA_LOGS.output( - #ctx, - &buf.buf[..record_len], 0 - )} - } - }; + } + + let num_args = values.len(); + let values_iter = values.iter(); Ok(quote! { { @@ -139,13 +146,21 @@ pub(crate) fn log(args: LogArgs, level: Option) -> Result(record_len) #( .and_then(|record_len| { + if record_len >= buf.buf.len() { + return Err(()); + } + { #values_iter }.write(&mut buf.buf[record_len..]).map(|len| record_len + len) + }) )* + } { + unsafe { ::aya_log_ebpf::AYA_LOGS.output( + #ctx, + &buf.buf[..record_len], 0 + )} } } } diff --git a/aya-log-parser/Cargo.toml b/aya-log-parser/Cargo.toml new file mode 100644 index 00000000..d76ae6b0 --- /dev/null +++ b/aya-log-parser/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "aya-log-parser" +version = "0.1.11-dev.0" +edition = "2018" + +[dependencies] +aya-log-common = { path = "../aya-log-common" } + +[lib] +path = "src/lib.rs" diff --git a/aya-log-parser/src/lib.rs b/aya-log-parser/src/lib.rs new file mode 100644 index 00000000..16ca8ec6 --- /dev/null +++ b/aya-log-parser/src/lib.rs @@ -0,0 +1,177 @@ +use std::str; + +use aya_log_common::DisplayHint; + +/// A parsed formatting parameter (contents of `{` `}` block). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Parameter { + /// The display hint, e.g. ':ipv4', ':x'. + pub hint: DisplayHint, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Fragment { + /// A literal string (eg. `"literal "` in `"literal {}"`). + Literal(String), + + /// A format parameter. + Parameter(Parameter), +} + +fn push_literal(frag: &mut Vec, unescaped_literal: &str) -> Result<(), String> { + // Replace `{{` with `{` and `}}` with `}`. Single braces are errors. + + // Scan for single braces first. The rest is trivial. + let mut last_open = false; + let mut last_close = false; + for c in unescaped_literal.chars() { + match c { + '{' => last_open = !last_open, + '}' => last_close = !last_close, + _ => { + if last_open { + return Err("unmatched `{` in format string".into()); + } + if last_close { + return Err("unmatched `}` in format string".into()); + } + } + } + } + + // Handle trailing unescaped `{` or `}`. + if last_open { + return Err("unmatched `{` in format string".into()); + } + if last_close { + return Err("unmatched `}` in format string".into()); + } + + let literal = unescaped_literal.replace("{{", "{").replace("}}", "}"); + frag.push(Fragment::Literal(literal)); + Ok(()) +} + +/// Parses the display hint (e.g. the `ipv4` in `{:ipv4}`). +fn parse_display_hint(s: &str) -> Result { + Ok(match s { + "x" => DisplayHint::LowerHex, + "X" => DisplayHint::UpperHex, + "ipv4" => DisplayHint::Ipv4, + "ipv6" => DisplayHint::Ipv6, + _ => return Err(format!("unknown display hint: {:?}", s)), + }) +} + +/// Parse `Param` from the given `&str` which can specify an optional format +/// like `:x` or `:ipv4` (without curly braces, which are parsed by the `parse` +/// function). +fn parse_param(mut input: &str) -> Result { + const HINT_PREFIX: &str = ":"; + + // Then, optional hint + let mut hint = DisplayHint::Default; + + if input.starts_with(HINT_PREFIX) { + // skip the prefix + input = &input[HINT_PREFIX.len()..]; + if input.is_empty() { + return Err("malformed format string (missing display hint after ':')".into()); + } + + hint = parse_display_hint(input)?; + } else if !input.is_empty() { + return Err(format!("unexpected content {:?} in format string", input)); + } + + Ok(Parameter { hint }) +} + +/// Parses the given format string into string literals and parameters specified +/// by curly braces (with optional format hints like `:x` or `:ipv4`). +pub fn parse(format_string: &str) -> Result, String> { + let mut fragments = Vec::new(); + + // Index after the `}` of the last format specifier. + let mut end_pos = 0; + + let mut chars = format_string.char_indices(); + while let Some((brace_pos, ch)) = chars.next() { + if ch != '{' { + // Part of a literal fragment. + continue; + } + + // Peek at the next char. + if chars.as_str().starts_with('{') { + // Escaped `{{`, also part of a literal fragment. + chars.next(); + continue; + } + + if brace_pos > end_pos { + // There's a literal fragment with at least 1 character before this + // parameter fragment. + let unescaped_literal = &format_string[end_pos..brace_pos]; + push_literal(&mut fragments, unescaped_literal)?; + } + + // Else, this is a format specifier. It ends at the next `}`. + let len = chars + .as_str() + .find('}') + .ok_or("missing `}` in format string")?; + end_pos = brace_pos + 1 + len + 1; + + // Parse the contents inside the braces. + let param_str = &format_string[brace_pos + 1..][..len]; + let param = parse_param(param_str)?; + fragments.push(Fragment::Parameter(param)); + } + + // Trailing literal. + if end_pos != format_string.len() { + push_literal(&mut fragments, &format_string[end_pos..])?; + } + + Ok(fragments) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse() { + assert_eq!( + parse("foo {} bar {:x} test {:X} ayy {:ipv4} lmao {:ipv6} {{}} {{something}}"), + Ok(vec![ + Fragment::Literal("foo ".into()), + Fragment::Parameter(Parameter { + hint: DisplayHint::Default + }), + Fragment::Literal(" bar ".into()), + Fragment::Parameter(Parameter { + hint: DisplayHint::LowerHex + }), + Fragment::Literal(" test ".into()), + Fragment::Parameter(Parameter { + hint: DisplayHint::UpperHex + }), + Fragment::Literal(" ayy ".into()), + Fragment::Parameter(Parameter { + hint: DisplayHint::Ipv4 + }), + Fragment::Literal(" lmao ".into()), + Fragment::Parameter(Parameter { + hint: DisplayHint::Ipv6 + }), + Fragment::Literal(" {{}} {{something}}".into()), + ]) + ); + assert!(parse("foo {:}").is_err()); + assert!(parse("foo { bar").is_err()); + assert!(parse("foo } bar").is_err()); + assert!(parse("foo { bar }").is_err()); + } +} diff --git a/aya-log/Cargo.toml b/aya-log/Cargo.toml index 9f5298fc..93aea66f 100644 --- a/aya-log/Cargo.toml +++ b/aya-log/Cargo.toml @@ -13,7 +13,6 @@ edition = "2021" [dependencies] aya = { path = "../aya", version = "0.11.0", features=["async_tokio"] } aya-log-common = { path = "../aya-log-common", version = "0.1.11-dev.0", features=["userspace"] } -dyn-fmt = "0.3.0" thiserror = "1" log = "0.4" bytes = "1.1" diff --git a/aya-log/src/lib.rs b/aya-log/src/lib.rs index df924a3a..5786955c 100644 --- a/aya-log/src/lib.rs +++ b/aya-log/src/lib.rs @@ -49,11 +49,16 @@ //! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html //! [log]: https://docs.rs/log //! -use std::{io, mem, ptr, str, sync::Arc}; +use std::{ + fmt::{LowerHex, UpperHex}, + io, mem, + net::{Ipv4Addr, Ipv6Addr}, + ptr, slice, str, + sync::Arc, +}; -use aya_log_common::{ArgType, RecordField, LOG_BUF_CAPACITY, LOG_FIELDS}; +use aya_log_common::{Argument, DisplayHint, RecordField, LOG_BUF_CAPACITY, LOG_FIELDS}; use bytes::BytesMut; -use dyn_fmt::AsStrFormatExt; use log::{error, Level, Log, Record}; use thiserror::Error; @@ -112,6 +117,151 @@ impl BpfLogger { } } +pub trait Formatter { + fn format(v: T) -> String; +} + +pub struct DefaultFormatter; +impl Formatter for DefaultFormatter +where + T: ToString, +{ + fn format(v: T) -> String { + v.to_string() + } +} + +pub struct LowerHexFormatter; +impl Formatter for LowerHexFormatter +where + T: LowerHex, +{ + fn format(v: T) -> String { + format!("{:x}", v) + } +} + +pub struct UpperHexFormatter; +impl Formatter for UpperHexFormatter +where + T: UpperHex, +{ + fn format(v: T) -> String { + format!("{:X}", v) + } +} + +pub struct Ipv4Formatter; +impl Formatter for Ipv4Formatter +where + T: Into, +{ + fn format(v: T) -> String { + v.into().to_string() + } +} + +pub struct Ipv6Formatter; +impl Formatter for Ipv6Formatter +where + T: Into, +{ + fn format(v: T) -> String { + v.into().to_string() + } +} + +trait Format { + fn format(&self, last_hint: Option) -> Result; +} + +impl Format for u32 { + fn format(&self, last_hint: Option) -> Result { + match last_hint { + Some(DisplayHint::Default) => Ok(DefaultFormatter::format(self)), + Some(DisplayHint::LowerHex) => Ok(LowerHexFormatter::format(self)), + Some(DisplayHint::UpperHex) => Ok(UpperHexFormatter::format(self)), + Some(DisplayHint::Ipv4) => Ok(Ipv4Formatter::format(*self)), + Some(DisplayHint::Ipv6) => Err(()), + _ => Ok(DefaultFormatter::format(self)), + } + } +} + +impl Format for [u8; 16] { + fn format(&self, last_hint: Option) -> Result { + match last_hint { + Some(DisplayHint::Default) => Err(()), + Some(DisplayHint::LowerHex) => Err(()), + Some(DisplayHint::UpperHex) => Err(()), + Some(DisplayHint::Ipv4) => Err(()), + Some(DisplayHint::Ipv6) => Ok(Ipv6Formatter::format(*self)), + _ => Err(()), + } + } +} + +impl Format for [u16; 8] { + fn format(&self, last_hint: Option) -> Result { + match last_hint { + Some(DisplayHint::Default) => Err(()), + Some(DisplayHint::LowerHex) => Err(()), + Some(DisplayHint::UpperHex) => Err(()), + Some(DisplayHint::Ipv4) => Err(()), + Some(DisplayHint::Ipv6) => Ok(Ipv6Formatter::format(*self)), + _ => Err(()), + } + } +} + +macro_rules! impl_format { + ($type:ident) => { + impl Format for $type { + fn format(&self, last_hint: Option) -> Result { + match last_hint { + Some(DisplayHint::Default) => Ok(DefaultFormatter::format(self)), + Some(DisplayHint::LowerHex) => Ok(LowerHexFormatter::format(self)), + Some(DisplayHint::UpperHex) => Ok(UpperHexFormatter::format(self)), + Some(DisplayHint::Ipv4) => Err(()), + Some(DisplayHint::Ipv6) => Err(()), + _ => Ok(DefaultFormatter::format(self)), + } + } + } + }; +} + +impl_format!(i8); +impl_format!(i16); +impl_format!(i32); +impl_format!(i64); +impl_format!(isize); + +impl_format!(u8); +impl_format!(u16); +impl_format!(u64); +impl_format!(usize); + +macro_rules! impl_format_float { + ($type:ident) => { + impl Format for $type { + fn format(&self, last_hint: Option) -> Result { + match last_hint { + Some(DisplayHint::Default) => Ok(DefaultFormatter::format(self)), + Some(DisplayHint::LowerHex) => Err(()), + Some(DisplayHint::UpperHex) => Err(()), + Some(DisplayHint::Ipv4) => Err(()), + Some(DisplayHint::Ipv6) => Err(()), + _ => Ok(DefaultFormatter::format(self)), + } + } + } + }; +} + +impl_format_float!(f32); +impl_format_float!(f64); + #[derive(Copy, Clone, Debug)] struct DefaultLogger; @@ -147,7 +297,6 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { let mut module = None; let mut file = None; let mut line = None; - let mut log = None; let mut num_args = None; for _ in 0..LOG_FIELDS { @@ -172,97 +321,113 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> { RecordField::NumArgs => { num_args = Some(usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?)); } - RecordField::Log => { - log = Some(std::str::from_utf8(attr.value).map_err(|_| ())?); - } } buf = rest; } - let log_msg = log.ok_or(())?; - let full_log_msg = match num_args { - Some(n) => { - let mut args: Vec = Vec::new(); - for _ in 0..n { - let (attr, rest) = unsafe { TagLenValue::<'_, ArgType>::try_read(buf)? }; - - match attr.tag { - ArgType::I8 => { - args.push( - i8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::I16 => { - args.push( - i16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::I32 => { - args.push( - i32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::I64 => { - args.push( - i64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::Isize => { - args.push( - isize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) - .to_string(), - ); - } - ArgType::U8 => { - args.push( - u8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::U16 => { - args.push( - u16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::U32 => { - args.push( - u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::U64 => { - args.push( - u64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::Usize => { - args.push( - usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) - .to_string(), - ); - } - ArgType::F32 => { - args.push( - f32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::F64 => { - args.push( - f64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?).to_string(), - ); - } - ArgType::Str => match str::from_utf8(attr.value) { - Ok(v) => args.push(v.to_string()), - Err(e) => error!("received invalid utf8 string: {}", e), - }, - } + let mut full_log_msg = String::new(); + let mut last_hint: Option = None; + for _ in 0..num_args.ok_or(())? { + let (attr, rest) = unsafe { TagLenValue::<'_, Argument>::try_read(buf)? }; - buf = rest; + match attr.tag { + Argument::DisplayHint => { + last_hint = Some(unsafe { ptr::read_unaligned(attr.value.as_ptr() as *const _) }); } - - log_msg.format(&args) + Argument::I8 => { + full_log_msg.push_str( + &i8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::I16 => { + full_log_msg.push_str( + &i16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::I32 => { + full_log_msg.push_str( + &i32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::I64 => { + full_log_msg.push_str( + &i64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::Isize => { + full_log_msg.push_str( + &isize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::U8 => { + full_log_msg.push_str( + &u8::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::U16 => { + full_log_msg.push_str( + &u16::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::U32 => { + full_log_msg.push_str( + &u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::U64 => { + full_log_msg.push_str( + &u64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::Usize => { + full_log_msg.push_str( + &usize::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::F32 => { + full_log_msg.push_str( + &f32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::F64 => { + full_log_msg.push_str( + &f64::from_ne_bytes(attr.value.try_into().map_err(|_| ())?) + .format(last_hint.take())?, + ); + } + Argument::ArrU8Len16 => { + let value: [u8; 16] = attr.value.try_into().map_err(|_| ())?; + full_log_msg.push_str(&value.format(last_hint.take())?); + } + Argument::ArrU16Len8 => { + let ptr = attr.value.as_ptr().cast::(); + let slice = unsafe { slice::from_raw_parts(ptr, 8) }; + let mut value: [u16; 8] = Default::default(); + value.copy_from_slice(slice); + full_log_msg.push_str(&value.format(last_hint.take())?); + } + Argument::Str => match str::from_utf8(attr.value) { + Ok(v) => { + full_log_msg.push_str(v); + } + Err(e) => error!("received invalid utf8 string: {}", e), + }, } - None => log_msg.to_string(), - }; + + buf = rest; + } logger.log( &Record::builder() @@ -312,13 +477,13 @@ impl<'a, T: Pod> TagLenValue<'a, T> { #[cfg(test)] mod test { use super::*; - use aya_log_common::{write_record_header, write_record_message, WriteToBuf}; + use aya_log_common::{write_record_header, WriteToBuf}; use log::logger; use testing_logger; - fn new_log(msg: &str, args: usize) -> Result<(usize, Vec), ()> { + fn new_log(args: usize) -> Result<(usize, Vec), ()> { let mut buf = vec![0; 8192]; - let mut len = write_record_header( + let len = write_record_header( &mut buf, "test", aya_log_common::Level::Info, @@ -327,14 +492,18 @@ mod test { 123, args, )?; - len += write_record_message(&mut buf[len..], msg)?; Ok((len, buf)) } #[test] fn test_str() { testing_logger::setup(); - let (_, input) = new_log("test", 0).unwrap(); + let (len, mut input) = new_log(1).unwrap(); + + "test" + .write(&mut input[len..]) + .expect("could not write to the buffer"); + let logger = logger(); let _ = log_buf(&input, logger); testing_logger::validate(|captured_logs| { @@ -347,9 +516,13 @@ mod test { #[test] fn test_str_with_args() { testing_logger::setup(); - let (len, mut input) = new_log("hello {}", 1).unwrap(); - let name = "test"; - (*name).write(&mut input[len..]).unwrap(); + let (mut len, mut input) = new_log(2).unwrap(); + + len += "hello " + .write(&mut input[len..]) + .expect("could not write to the buffer"); + "test".write(&mut input[len..]).unwrap(); + let logger = logger(); let _ = log_buf(&input, logger); testing_logger::validate(|captured_logs| { @@ -358,4 +531,122 @@ mod test { assert_eq!(captured_logs[0].level, Level::Info); }); } + + #[test] + fn test_display_hint_default() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "default hint: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::Default.write(&mut input[len..]).unwrap(); + 14.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "default hint: 14"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_display_hint_lower_hex() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "lower hex: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::LowerHex.write(&mut input[len..]).unwrap(); + 200.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "lower hex: c8"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_display_hint_upper_hex() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "upper hex: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::UpperHex.write(&mut input[len..]).unwrap(); + 200.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "upper hex: C8"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_display_hint_ipv4() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "ipv4: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::Ipv4.write(&mut input[len..]).unwrap(); + // 10.0.0.1 as u32 + 167772161u32.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "ipv4: 10.0.0.1"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_display_hint_ipv6_arr_u8_len_16() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "ipv6: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::Ipv6.write(&mut input[len..]).unwrap(); + // 2001:db8::1:1 as byte array + let ipv6_arr: [u8; 16] = [ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, + ]; + ipv6_arr.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "ipv6: 2001:db8::1:1"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } + + #[test] + fn test_display_hint_ipv6_arr_u16_len_8() { + testing_logger::setup(); + let (mut len, mut input) = new_log(3).unwrap(); + + len += "ipv6: ".write(&mut input[len..]).unwrap(); + len += DisplayHint::Ipv6.write(&mut input[len..]).unwrap(); + // 2001:db8::1:1 as u16 array + let ipv6_arr: [u16; 8] = [ + 0x2001, 0x0db8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, + ]; + ipv6_arr.write(&mut input[len..]).unwrap(); + + let logger = logger(); + let _ = log_buf(&input, logger); + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 1); + assert_eq!(captured_logs[0].body, "ipv6: 2001:db8::1:1"); + assert_eq!(captured_logs[0].level, Level::Info); + }); + } } diff --git a/bpf/aya-log-ebpf/src/lib.rs b/bpf/aya-log-ebpf/src/lib.rs index 6faa949d..526aed48 100644 --- a/bpf/aya-log-ebpf/src/lib.rs +++ b/bpf/aya-log-ebpf/src/lib.rs @@ -3,9 +3,7 @@ use aya_bpf::{ macros::map, maps::{PerCpuArray, PerfEventByteArray}, }; -pub use aya_log_common::{ - write_record_header, write_record_message, Level, WriteToBuf, LOG_BUF_CAPACITY, -}; +pub use aya_log_common::{write_record_header, Level, WriteToBuf, LOG_BUF_CAPACITY}; pub use aya_log_ebpf_macros::{debug, error, info, log, trace, warn}; #[doc(hidden)] @@ -24,6 +22,6 @@ pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0); #[doc(hidden)] pub mod macro_support { - pub use aya_log_common::{Level, LOG_BUF_CAPACITY}; + pub use aya_log_common::{DisplayHint, Level, LOG_BUF_CAPACITY}; pub use aya_log_ebpf_macros::log; }