Browse Source

Format arguments in userspace

This change moves away argument formatting from eBPF to the userspace.
eBPF part of aya-log writes unformatted log message and all arguments to
the perf buffer and the userspace part of aya-log is formatting the
message after receiving all arguments.

Aya-based project to test this change:

https://github.com/vadorovsky/aya-log-example

Fixes: #4
Signed-off-by: Michal Rostecki <[email protected]>
Signed-off-by: Tuetuopay <[email protected]>
Co-authored-by: Tuetuopay <[email protected]>
Michal Rostecki 3 years ago
parent
commit
ca1fe7e

+ 31 - 2
aya-log/aya-log-common/src/lib.rs

@@ -2,7 +2,7 @@
 
 pub const LOG_BUF_CAPACITY: usize = 1024;
 
-pub const LOG_FIELDS: usize = 6;
+pub const LOG_FIELDS: usize = 7;
 
 #[repr(usize)]
 #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
@@ -37,8 +37,37 @@ pub enum RecordField {
     Module,
     File,
     Line,
+    NumArgs,
     Log,
 }
 
+#[repr(usize)]
+#[derive(Copy, Clone, Debug)]
+pub enum ArgType {
+    I8,
+    I16,
+    I32,
+    I64,
+    I128,
+    Isize,
+
+    U8,
+    U16,
+    U32,
+    U64,
+    U128,
+    Usize,
+
+    F32,
+    F64,
+
+    Str,
+}
+
 #[cfg(feature = "userspace")]
-unsafe impl aya::Pod for RecordField {}
+mod userspace {
+    use super::*;
+
+    unsafe impl aya::Pod for RecordField {}
+    unsafe impl aya::Pod for ArgType {}
+}

+ 0 - 130
aya-log/aya-log-ebpf/src/lib.rs

@@ -1,130 +0,0 @@
-#![no_std]
-
-pub extern crate ufmt;
-
-mod macros;
-
-use core::{cmp, mem, ptr};
-
-use aya_bpf::{
-    macros::map,
-    maps::{PerCpuArray, PerfEventByteArray},
-};
-pub use aya_log_common::Level;
-use aya_log_common::RecordField;
-pub use aya_log_common::LOG_BUF_CAPACITY;
-
-#[doc(hidden)]
-#[repr(C)]
-pub struct LogBuf {
-    pub buf: [u8; LOG_BUF_CAPACITY],
-}
-
-#[doc(hidden)]
-#[map]
-pub static mut AYA_LOG_BUF: PerCpuArray<LogBuf> = PerCpuArray::with_max_entries(1, 0);
-
-#[doc(hidden)]
-#[map]
-pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0);
-
-#[doc(hidden)]
-pub struct LogBufWriter<'a> {
-    pos: usize,
-    data: &'a mut [u8],
-}
-
-impl<'a> LogBufWriter<'a> {
-    pub fn new(data: &mut [u8]) -> LogBufWriter<'_> {
-        LogBufWriter {
-            pos: mem::size_of::<RecordField>() + mem::size_of::<usize>(),
-            data,
-        }
-    }
-
-    pub fn finish(self) -> usize {
-        let mut buf = self.data;
-        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, RecordField::Log) };
-        buf = &mut buf[mem::size_of::<RecordField>()..];
-
-        let len = self.pos - mem::size_of::<RecordField>() - mem::size_of::<usize>();
-        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, len) };
-
-        self.pos
-    }
-}
-
-impl<'a> ufmt::uWrite for LogBufWriter<'a> {
-    type Error = ();
-
-    fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
-        let bytes = s.as_bytes();
-        let len = bytes.len();
-
-        // this is to make sure the verifier knows about the upper bound
-        if len > LOG_BUF_CAPACITY {
-            return Err(());
-        }
-
-        let available = self.data.len() - self.pos;
-        if available < len {
-            return Err(());
-        }
-
-        self.data[self.pos..self.pos + len].copy_from_slice(&bytes[..len]);
-        self.pos += len;
-        Ok(())
-    }
-}
-
-struct TagLenValue<'a> {
-    tag: RecordField,
-    value: &'a [u8],
-}
-
-impl<'a> TagLenValue<'a> {
-    #[inline(always)]
-    pub(crate) fn new(tag: RecordField, value: &'a [u8]) -> TagLenValue<'a> {
-        TagLenValue { tag, value }
-    }
-
-    pub(crate) fn try_write(&self, mut buf: &mut [u8]) -> Result<usize, ()> {
-        let size = mem::size_of::<RecordField>() + mem::size_of::<usize>() + self.value.len();
-        if buf.len() < size {
-            return Err(());
-        }
-
-        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.tag) };
-        buf = &mut buf[mem::size_of::<RecordField>()..];
-
-        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) };
-        buf = &mut buf[mem::size_of::<usize>()..];
-
-        let len = cmp::min(buf.len(), self.value.len());
-        buf[..len].copy_from_slice(&self.value[..len]);
-        Ok(size)
-    }
-}
-
-#[doc(hidden)]
-pub fn write_record_header(
-    buf: &mut [u8],
-    target: &str,
-    level: Level,
-    module: &str,
-    file: &str,
-    line: u32,
-) -> Result<usize, ()> {
-    let mut size = 0;
-    for attr in [
-        TagLenValue::new(RecordField::Target, target.as_bytes()),
-        TagLenValue::new(RecordField::Level, &(level as usize).to_ne_bytes()),
-        TagLenValue::new(RecordField::Module, module.as_bytes()),
-        TagLenValue::new(RecordField::File, file.as_bytes()),
-        TagLenValue::new(RecordField::Line, &line.to_ne_bytes()),
-    ] {
-        size += attr.try_write(&mut buf[size..])?;
-    }
-
-    Ok(size)
-}

+ 0 - 205
aya-log/aya-log-ebpf/src/macros.rs

@@ -1,205 +0,0 @@
-/// Logs a message at the error level.
-///
-/// # Examples
-///
-/// ```no_run
-/// # let ctx = ();
-/// # let err_code = -1;
-/// use aya_log_ebpf::error;
-///
-/// error!(&ctx, "Error redirecting packet: {}", err_code);
-/// error!(&ctx, target: "ingress", "Error redirecting packet: {}", err_code);
-/// ```
-#[macro_export]
-macro_rules! error {
-    ($ctx:expr, target: $target:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, target: $target, $crate::Level::Error, $($arg)+)
-    );
-    ($ctx:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, $crate::Level::Error, $($arg)+)
-    )
-}
-
-/// Logs a message at the warn level.
-///
-/// # Examples
-///
-/// ```
-/// use aya_log_ebpf::warn;
-///
-/// # fn main() {
-/// let warn_description = "Invalid Input";
-///
-/// warn!("Warning! {}!", warn_description);
-/// warn!(target: "input_events", "App received warning: {}", warn_description);
-/// # }
-/// ```
-#[macro_export]
-macro_rules! warn {
-    ($ctx:expr, target: $target:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, target: $target, $crate::Level::Warn, $($arg)+)
-    );
-    ($ctx:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, $crate::Level::Warn, $($arg)+)
-    )
-}
-
-/// Logs a message at the info level.
-///
-/// # Examples
-///
-/// ```edition2018
-/// use log::info;
-///
-/// # fn main() {
-/// # struct Connection { port: u32, speed: u32 }
-/// let conn_info = Connection { port: 40, speed: 3 };
-///
-/// info!("Connected to port {} at {} Mb/s", conn_info.port, conn_info.speed);
-/// info!(target: "connection_events", "Successfull connection, port: {}, speed: {}",
-///       conn_info.port, conn_info.speed);
-/// # }
-/// ```
-#[macro_export]
-macro_rules! info {
-    ($ctx:expr, target: $target:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, target: $target, $crate::Level::Info, $($arg)+)
-    );
-    ($ctx:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, $crate::Level::Info, $($arg)+)
-    )
-}
-
-/// Logs a message at the debug level.
-///
-/// # Examples
-///
-/// ```edition2018
-/// use log::debug;
-///
-/// # fn main() {
-/// # struct Position { x: i64, y: i64 }
-/// let pos = Position { x: 3.234, y: -1223 };
-///
-/// debug!("New position: x: {}, y: {}", pos.x, pos.y);
-/// debug!(target: "app_events", "New position: x: {}, y: {}", pos.x, pos.y);
-/// # }
-/// ```
-#[macro_export]
-macro_rules! debug {
-    ($ctx:expr, target: $target:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, target: $target, $crate::Level::Debug, $($arg)+)
-    );
-    ($ctx:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, $crate::Level::Debug, $($arg)+)
-    )
-}
-
-/// Logs a message at the trace level.
-///
-/// # Examples
-///
-/// ```edition2018
-/// use log::trace;
-///
-/// # fn main() {
-/// # struct Position { x: i64, y: i64 }
-/// let pos = Position { x: 3234, y: -1223 };
-///
-/// trace!("Position is: x: {}, y: {}", pos.x, pos.y);
-/// trace!(target: "app_events", "x is {} and y is {}",
-///        if pos.x >= 0 { "positive" } else { "negative" },
-///        if pos.y >= 0 { "positive" } else { "negative" });
-/// # }
-/// ```
-#[macro_export]
-macro_rules! trace {
-    ($ctx:expr, target: $target:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, target: $target, $crate::Level::Trace, $($arg)+)
-    );
-    ($ctx:expr, $($arg:tt)+) => (
-        $crate::log!($ctx, $crate::Level::Trace, $($arg)+)
-    )
-}
-
-// /// Determines if a message logged at the specified level in that module will
-// /// be logged.
-// ///
-// /// This can be used to avoid expensive computation of log message arguments if
-// /// the message would be ignored anyway.
-// ///
-// /// # Examples
-// ///
-// /// ```edition2018
-// /// use log::Level::Debug;
-// /// use log::{debug, log_enabled};
-// ///
-// /// # fn foo() {
-// /// if log_enabled!(Debug) {
-// ///     let data = expensive_call();
-// ///     debug!("expensive debug data: {} {}", data.x, data.y);
-// /// }
-// /// if log_enabled!(target: "Global", Debug) {
-// ///    let data = expensive_call();
-// ///    debug!(target: "Global", "expensive debug data: {} {}", data.x, data.y);
-// /// }
-// /// # }
-// /// # struct Data { x: u32, y: u32 }
-// /// # fn expensive_call() -> Data { Data { x: 0, y: 0 } }
-// /// # fn main() {}
-// /// ```
-// macro_rules! log_enabled {
-//     (target: $target:expr, $lvl:expr) => {{
-//         let lvl = $lvl;
-//         lvl <= $crate::STATIC_MAX_LEVEL
-//     }};
-//     ($lvl:expr) => {
-//         log_enabled!(target: __log_module_path!(), $lvl)
-//     };
-// }
-
-/// Log a message at the given level.
-///
-/// This macro will generically log with the specified `Level` and `format!`
-/// based argument list.
-///
-/// # Examples
-///
-/// ```edition2018
-/// use log::{log, Level};
-///
-/// # fn main() {
-/// let data = (42, "Forty-two");
-/// let private_data = "private";
-///
-/// log!(Level::Error, "Received errors: {}, {}", data.0, data.1);
-/// log!(target: "app_events", Level::Warn, "App warning: {}, {}, {}",
-///     data.0, data.1, private_data);
-/// # }
-/// ```
-#[macro_export]
-macro_rules! log {
-    ($ctx:expr, target: $target:expr, $lvl:expr, $($arg:tt)+) => ({
-        if let Some(buf) = unsafe { $crate::AYA_LOG_BUF.get_mut(0) } {
-            if let Ok(header_len) = $crate::write_record_header(&mut buf.buf, module_path!(), $lvl, module_path!(), file!(), line!()) {
-                if let Ok(message_len) = $crate::write_record_message!(&mut buf.buf[header_len..], $($arg)+) {
-                    let record_len = header_len + message_len;
-                    if record_len <= $crate::LOG_BUF_CAPACITY {
-                        let _ = unsafe { $crate::AYA_LOGS.output($ctx, &buf.buf[..record_len], 0) };
-                    }
-                };
-            }
-        }
-    });
-    ($ctx:expr, $lvl:expr, $($arg:tt)+) => ($crate::log!($ctx, target: __log_module_path!(), $lvl, $($arg)+))
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! write_record_message {
-    ($buf:expr, $($arg:tt)+) => {{
-        use aya_log_ebpf::ufmt;
-        let mut writer = $crate::LogBufWriter::new($buf);
-        aya_log_ebpf::ufmt::uwrite!(writer, $($arg)+).map(|_| writer.finish())
-    }}
-}

+ 1 - 0
aya-log/aya-log/Cargo.toml

@@ -13,6 +13,7 @@ edition = "2018"
 [dependencies]
 aya = { version = "0.10.5", features=["async_tokio"] }
 aya-log-common = { version = "0.1", path = "../aya-log-common", features=["userspace"] }
+dyn-fmt = "0.3.0"
 thiserror = "1"
 log = "0.4"
 bytes = "1.1"

+ 103 - 4
aya-log/aya-log/src/lib.rs

@@ -59,11 +59,12 @@
 //! [Log]: https://docs.rs/log/0.4.14/log/trait.Log.html
 //! [log]: https://docs.rs/log
 //!
-use std::{convert::TryInto, io, mem, ptr, sync::Arc};
+use std::{convert::TryInto, io, mem, ptr, str, sync::Arc};
 
-use aya_log_common::{RecordField, LOG_BUF_CAPACITY, LOG_FIELDS};
+use aya_log_common::{ArgType, RecordField, LOG_BUF_CAPACITY, LOG_FIELDS};
 use bytes::BytesMut;
-use log::{Level, Log, Record};
+use dyn_fmt::AsStrFormatExt;
+use log::{error, Level, Log, Record};
 use thiserror::Error;
 
 use aya::{
@@ -157,6 +158,7 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> {
     let mut file = None;
     let mut line = None;
     let mut log = None;
+    let mut num_args = None;
 
     for _ in 0..LOG_FIELDS {
         let (attr, rest) = unsafe { TagLenValue::<'_, RecordField>::try_read(buf)? };
@@ -177,6 +179,9 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> {
             RecordField::Line => {
                 line = Some(u32::from_ne_bytes(attr.value.try_into().map_err(|_| ())?));
             }
+            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(|_| ())?);
             }
@@ -185,9 +190,103 @@ fn log_buf(mut buf: &[u8], logger: &dyn Log) -> Result<(), ()> {
         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::I128 => {
+                        args.push(
+                            i128::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::U128 => {
+                        args.push(
+                            u128::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),
+                    },
+                }
+
+                buf = rest;
+            }
+
+            log_msg.format(&args)
+        }
+        None => log_msg.to_string(),
+    };
+
     logger.log(
         &Record::builder()
-            .args(format_args!("{}", log.ok_or(())?))
+            .args(format_args!("{}", full_log_msg))
             .target(target.ok_or(())?)
             .level(level)
             .module_path(module)

+ 0 - 0
aya-log/aya-log-ebpf/.cargo/config.toml → aya-log/ebpf/.cargo/config.toml


+ 2 - 0
aya-log/ebpf/Cargo.toml

@@ -0,0 +1,2 @@
+[workspace]
+members = ["aya-log-ebpf", "aya-log-ebpf-macros"]

+ 12 - 0
aya-log/ebpf/aya-log-ebpf-macros/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "aya-log-ebpf-macros"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = "1.0"
+
+[lib]
+proc-macro = true

+ 185 - 0
aya-log/ebpf/aya-log-ebpf-macros/src/expand.rs

@@ -0,0 +1,185 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{
+    parse::{Parse, ParseStream},
+    punctuated::Punctuated,
+    Error, Expr, LitStr, Result, Token,
+};
+
+pub(crate) struct LogArgs {
+    pub(crate) ctx: Expr,
+    pub(crate) target: Option<Expr>,
+    pub(crate) level: Option<Expr>,
+    pub(crate) format_string: LitStr,
+    pub(crate) formatting_args: Option<Punctuated<Expr, Token![,]>>,
+}
+
+mod kw {
+    syn::custom_keyword!(target);
+}
+
+impl Parse for LogArgs {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let ctx: Expr = input.parse()?;
+        input.parse::<Token![,]>()?;
+
+        // Parse `target: &str`, which is an optional argument.
+        let target: Option<Expr> = if input.peek(kw::target) {
+            input.parse::<kw::target>()?;
+            input.parse::<Token![:]>()?;
+            let t: Expr = input.parse()?;
+            input.parse::<Token![,]>()?;
+            Some(t)
+        } else {
+            None
+        };
+
+        // Check whether the next token is `format_string: &str` (which i
+        // always provided) or `level` (which is an optional expression).
+        // If `level` is provided, it comes before `format_string`.
+        let (level, format_string): (Option<Expr>, LitStr) = if input.peek(LitStr) {
+            // Only `format_string` is provided.
+            (None, input.parse()?)
+        } else {
+            // Both `level` and `format_string` are provided.
+            let level: Expr = input.parse()?;
+            input.parse::<Token![,]>()?;
+            let format_string: LitStr = input.parse()?;
+            (Some(level), format_string)
+        };
+
+        // Parse variadic arguments.
+        let formatting_args: Option<Punctuated<Expr, Token![,]>> = if input.is_empty() {
+            None
+        } else {
+            input.parse::<Token![,]>()?;
+            Some(Punctuated::parse_terminated(input)?)
+        };
+
+        Ok(Self {
+            ctx,
+            target,
+            level,
+            format_string,
+            formatting_args,
+        })
+    }
+}
+
+pub(crate) fn log(args: LogArgs, level: Option<TokenStream>) -> Result<TokenStream> {
+    let ctx = args.ctx;
+    let target = match args.target {
+        Some(t) => quote! { #t },
+        None => quote! { module_path!() },
+    };
+    let lvl: TokenStream = if let Some(l) = level {
+        l
+    } else if let Some(l) = args.level {
+        quote! { #l }
+    } else {
+        return Err(Error::new(
+            args.format_string.span(),
+            "missing `level` argument: try passing an `aya_log_ebpf::Level` value",
+        ));
+    };
+    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| {
+                    { #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
+                )}
+            }
+        }
+    } else {
+        // Writing with no variadic arguments.
+        quote! {
+            unsafe { ::aya_log_ebpf::AYA_LOGS.output(
+                #ctx,
+                &buf.buf[..record_len], 0
+            )}
+        }
+    };
+
+    Ok(quote! {
+        {
+            if let Some(buf) = unsafe { ::aya_log_ebpf::AYA_LOG_BUF.get_mut(0) } {
+                if let Ok(header_len) = ::aya_log_ebpf::write_record_header(
+                    &mut buf.buf,
+                    #target,
+                    #lvl,
+                    module_path!(),
+                    file!(),
+                    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
+                    }
+                }
+            }
+        }
+    })
+}
+
+pub(crate) fn error(args: LogArgs) -> Result<TokenStream> {
+    log(
+        args,
+        Some(quote! { ::aya_log_ebpf::macro_support::Level::Error }),
+    )
+}
+
+pub(crate) fn warn(args: LogArgs) -> Result<TokenStream> {
+    log(
+        args,
+        Some(quote! { ::aya_log_ebpf::macro_support::Level::Warn }),
+    )
+}
+
+pub(crate) fn info(args: LogArgs) -> Result<TokenStream> {
+    log(
+        args,
+        Some(quote! { ::aya_log_ebpf::macro_support::Level::Info }),
+    )
+}
+
+pub(crate) fn debug(args: LogArgs) -> Result<TokenStream> {
+    log(
+        args,
+        Some(quote! { ::aya_log_ebpf::macro_support::Level::Debug }),
+    )
+}
+
+pub(crate) fn trace(args: LogArgs) -> Result<TokenStream> {
+    log(
+        args,
+        Some(quote! { ::aya_log_ebpf::macro_support::Level::Trace }),
+    )
+}

+ 52 - 0
aya-log/ebpf/aya-log-ebpf-macros/src/lib.rs

@@ -0,0 +1,52 @@
+use proc_macro::TokenStream;
+use syn::parse_macro_input;
+
+mod expand;
+
+#[proc_macro]
+pub fn log(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::log(args, None)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
+
+#[proc_macro]
+pub fn error(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::error(args)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
+
+#[proc_macro]
+pub fn warn(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::warn(args)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
+
+#[proc_macro]
+pub fn info(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::info(args)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
+
+#[proc_macro]
+pub fn debug(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::debug(args)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}
+
+#[proc_macro]
+pub fn trace(args: TokenStream) -> TokenStream {
+    let args = parse_macro_input!(args as expand::LogArgs);
+    expand::trace(args)
+        .unwrap_or_else(|err| err.to_compile_error())
+        .into()
+}

+ 3 - 6
aya-log/aya-log-ebpf/Cargo.toml → aya-log/ebpf/aya-log-ebpf/Cargo.toml

@@ -5,8 +5,8 @@ edition = "2018"
 
 [dependencies]
 aya-bpf = { git = "https://github.com/aya-rs/aya", branch = "main" }
-aya-log-common = { path = "../aya-log-common" }
-ufmt = { version = "0.1", package = "aya-ufmt" }
+aya-log-common = { path = "../../aya-log-common" }
+aya-log-ebpf-macros = { path = "../aya-log-ebpf-macros" }
 
 [lib]
 path = "src/lib.rs"
@@ -18,7 +18,4 @@ opt-level = 2
 overflow-checks = false
 
 [profile.release]
-panic = "abort"
-
-[workspace]
-members = []
+panic = "abort"

+ 133 - 0
aya-log/ebpf/aya-log-ebpf/src/lib.rs

@@ -0,0 +1,133 @@
+#![no_std]
+
+use core::{cmp, mem, ptr};
+
+use aya_bpf::{
+    macros::map,
+    maps::{PerCpuArray, PerfEventByteArray},
+};
+use aya_log_common::{ArgType, RecordField};
+pub use aya_log_common::{Level, LOG_BUF_CAPACITY};
+pub use aya_log_ebpf_macros::{debug, error, info, log, trace, warn};
+
+#[doc(hidden)]
+#[repr(C)]
+pub struct LogBuf {
+    pub buf: [u8; LOG_BUF_CAPACITY],
+}
+
+#[doc(hidden)]
+#[map]
+pub static mut AYA_LOG_BUF: PerCpuArray<LogBuf> = PerCpuArray::with_max_entries(1, 0);
+
+#[doc(hidden)]
+#[map]
+pub static mut AYA_LOGS: PerfEventByteArray = PerfEventByteArray::new(0);
+
+struct TagLenValue<'a, T> {
+    tag: T,
+    value: &'a [u8],
+}
+
+impl<'a, T> TagLenValue<'a, T>
+where
+    T: Copy,
+{
+    #[inline(always)]
+    pub(crate) fn new(tag: T, value: &'a [u8]) -> TagLenValue<'a, T> {
+        TagLenValue { tag, value }
+    }
+
+    pub(crate) fn write(&self, mut buf: &mut [u8]) -> Result<usize, ()> {
+        let size = mem::size_of::<T>() + mem::size_of::<usize>() + self.value.len();
+        if buf.len() < size {
+            return Err(());
+        }
+
+        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.tag) };
+        buf = &mut buf[mem::size_of::<T>()..];
+
+        unsafe { ptr::write_unaligned(buf.as_mut_ptr() as *mut _, self.value.len()) };
+        buf = &mut buf[mem::size_of::<usize>()..];
+
+        let len = cmp::min(buf.len(), self.value.len());
+        buf[..len].copy_from_slice(&self.value[..len]);
+        Ok(size)
+    }
+}
+
+pub trait WriteToBuf {
+    #[allow(clippy::result_unit_err)]
+    fn write(&self, buf: &mut [u8]) -> Result<usize, ()>;
+}
+
+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)
+            }
+        }
+    };
+}
+
+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!(i128, ArgType::I128);
+impl_write_to_buf!(isize, ArgType::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!(u128, ArgType::U128);
+impl_write_to_buf!(usize, ArgType::Usize);
+
+impl_write_to_buf!(f32, ArgType::F32);
+impl_write_to_buf!(f64, ArgType::F64);
+
+impl WriteToBuf for str {
+    fn write(&self, buf: &mut [u8]) -> Result<usize, ()> {
+        TagLenValue::<ArgType>::new(ArgType::Str, self.as_bytes()).write(buf)
+    }
+}
+
+#[allow(clippy::result_unit_err)]
+#[doc(hidden)]
+pub fn write_record_header(
+    buf: &mut [u8],
+    target: &str,
+    level: Level,
+    module: &str,
+    file: &str,
+    line: u32,
+    num_args: usize,
+) -> Result<usize, ()> {
+    let mut size = 0;
+    for attr in [
+        TagLenValue::<RecordField>::new(RecordField::Target, target.as_bytes()),
+        TagLenValue::<RecordField>::new(RecordField::Level, &(level as usize).to_ne_bytes()),
+        TagLenValue::<RecordField>::new(RecordField::Module, module.as_bytes()),
+        TagLenValue::<RecordField>::new(RecordField::File, file.as_bytes()),
+        TagLenValue::<RecordField>::new(RecordField::Line, &line.to_ne_bytes()),
+        TagLenValue::<RecordField>::new(RecordField::NumArgs, &num_args.to_ne_bytes()),
+    ] {
+        size += attr.write(&mut buf[size..])?;
+    }
+
+    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)
+}
+
+#[doc(hidden)]
+pub mod macro_support {
+    pub use aya_log_common::{Level, LOG_BUF_CAPACITY};
+    pub use aya_log_ebpf_macros::log;
+}

+ 2 - 0
aya-log/ebpf/rust-toolchain.toml

@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"