Explorar el Código

lib: Add display hints

This change adds optional display hints:

* `{:x}`, `{:X}` - for hex representation of numbers
* `{:ipv4}`, `{:IPv4}` - for IPv4 addresses
* `{:ipv6}`, `{:IPv6}` - for IPv6 addresses

It also gets rid of dyn-fmt and instead comes with our own parser
implementation.

Tested on: https://github.com/vadorovsky/aya-examples/tree/main/tc

Signed-off-by: Michal Rostecki <[email protected]>
Michal Rostecki hace 2 años
padre
commit
83ec27f06b

+ 1 - 1
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

+ 1 - 0
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"

+ 63 - 25
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<usize, ()> {
-                TagLenValue::<ArgType>::new($arg_type, &self.to_ne_bytes()).write(buf)
+                TagLenValue::<Argument>::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<usize, ()> {
+        TagLenValue::<Argument>::new(Argument::ArrU8Len16, self).write(buf)
+    }
+}
+
+impl WriteToBuf for [u16; 8] {
+    fn write(&self, buf: &mut [u8]) -> Result<usize, ()> {
+        let ptr = self.as_ptr().cast::<u8>();
+        let bytes = unsafe { slice::from_raw_parts(ptr, 16) };
+        TagLenValue::<Argument>::new(Argument::ArrU16Len8, bytes).write(buf)
+    }
+}
 
 impl WriteToBuf for str {
     fn write(&self, buf: &mut [u8]) -> Result<usize, ()> {
-        TagLenValue::<ArgType>::new(ArgType::Str, self.as_bytes()).write(buf)
+        TagLenValue::<Argument>::new(Argument::Str, self.as_bytes()).write(buf)
+    }
+}
+
+impl WriteToBuf for DisplayHint {
+    fn write(&self, buf: &mut [u8]) -> Result<usize, ()> {
+        let v: u8 = (*self).into();
+        TagLenValue::<Argument>::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<usize, ()> {
-    TagLenValue::<RecordField>::new(RecordField::Log, msg.as_bytes()).write(buf)
-}

+ 2 - 0
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"

+ 61 - 46
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<Expr>,
@@ -66,6 +70,20 @@ impl Parse for LogArgs {
     }
 }
 
+fn string_to_expr(s: String) -> Result<Expr> {
+    parse_str(&format!("\"{}\"", s))
+}
+
+fn hint_to_expr(hint: DisplayHint) -> Result<Expr> {
+    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<TokenStream>) -> Result<TokenStream> {
     let ctx = args.ctx;
     let target = match args.target {
@@ -84,47 +102,36 @@ pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStre
     };
     let format_string = args.format_string;
 
-    let (num_args, write_args) = match args.formatting_args {
-        Some(formatting_args) => {
-            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<TokenStream>) -> Result<TokenStre
                     line!(),
                     #num_args,
                 ) {
-                    if let Ok(message_len) = ::aya_log_ebpf::write_record_message(
-                        &mut buf.buf[header_len..],
-                        #format_string,
-                    ) {
-                        let record_len = header_len + message_len;
-
-                        #write_to_perf_buffer
+                    let record_len = header_len;
+
+                    if let Ok(record_len) = {
+                        use ::aya_log_ebpf::WriteToBuf;
+                        Ok::<_, ()>(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
+                        )}
                     }
                 }
             }

+ 10 - 0
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"

+ 177 - 0
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<Fragment>, 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<DisplayHint, String> {
+    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<Parameter, String> {
+    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<Vec<Fragment>, 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());
+    }
+}

+ 0 - 1
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"

+ 386 - 95
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<T> {
+    fn format(v: T) -> String;
+}
+
+pub struct DefaultFormatter;
+impl<T> Formatter<T> for DefaultFormatter
+where
+    T: ToString,
+{
+    fn format(v: T) -> String {
+        v.to_string()
+    }
+}
+
+pub struct LowerHexFormatter;
+impl<T> Formatter<T> for LowerHexFormatter
+where
+    T: LowerHex,
+{
+    fn format(v: T) -> String {
+        format!("{:x}", v)
+    }
+}
+
+pub struct UpperHexFormatter;
+impl<T> Formatter<T> for UpperHexFormatter
+where
+    T: UpperHex,
+{
+    fn format(v: T) -> String {
+        format!("{:X}", v)
+    }
+}
+
+pub struct Ipv4Formatter;
+impl<T> Formatter<T> for Ipv4Formatter
+where
+    T: Into<Ipv4Addr>,
+{
+    fn format(v: T) -> String {
+        v.into().to_string()
+    }
+}
+
+pub struct Ipv6Formatter;
+impl<T> Formatter<T> for Ipv6Formatter
+where
+    T: Into<Ipv6Addr>,
+{
+    fn format(v: T) -> String {
+        v.into().to_string()
+    }
+}
+
+trait Format {
+    fn format(&self, last_hint: Option<DisplayHint>) -> Result<String, ()>;
+}
+
+impl Format for u32 {
+    fn format(&self, last_hint: Option<DisplayHint>) -> Result<String, ()> {
+        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<DisplayHint>) -> Result<String, ()> {
+        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<DisplayHint>) -> Result<String, ()> {
+        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<DisplayHint>) -> Result<String, ()> {
+                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<DisplayHint>) -> Result<String, ()> {
+                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<String> = 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<DisplayHint> = 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::<u16>();
+                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<u8>), ()> {
+    fn new_log(args: usize) -> Result<(usize, Vec<u8>), ()> {
         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);
+        });
+    }
 }

+ 2 - 4
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;
 }