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 <vadorovsky@gmail.com>
pull/372/head
Michal Rostecki 2 years ago committed by Alessandro Decina
parent 944d6b8a16
commit 83ec27f06b

@ -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

@ -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"

@ -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)
}

@ -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"

@ -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
)}
}
}
}

@ -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"

@ -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());
}
}

@ -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"

@ -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);
});
}
}

@ -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;
}

Loading…
Cancel
Save