use std::{
fmt,
hash::{Hash, Hasher},
io,
io::Write,
};
use nix::errno::Errno;
use super::WriteBuf;
#[derive(Clone, Debug)]
pub enum LogValue {
Null,
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Borrowed(&'static str),
Raw(Vec<u8>),
}
impl LogValue {
pub fn try_serialize<T: serde::Serialize>(val: &T) -> Result<Self, Errno> {
let mut buf = WriteBuf::new();
serde_json::to_writer(&mut buf, val).or(Err(Errno::ENOMEM))?;
match buf.0.first() {
Some(b'-' | b'0'..=b'9') => {
if let Ok(s) = std::str::from_utf8(&buf.0) {
if let Ok(n) = s.parse::<i64>() {
return Ok(Self::I64(n));
}
if let Ok(n) = s.parse::<u64>() {
return Ok(Self::U64(n));
}
if let Ok(n) = s.parse::<f64>() {
return Ok(Self::F64(n));
}
}
Ok(Self::Raw(buf.0))
}
Some(b't') => Ok(Self::Bool(true)),
Some(b'f') => Ok(Self::Bool(false)),
Some(b'n') => Ok(Self::Null),
Some(_) => Ok(Self::Raw(buf.0)),
None => Err(Errno::EINVAL),
}
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::I64(n) => Some(*n),
Self::U64(n) => i64::try_from(*n).ok(),
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
Self::U64(n) => Some(*n),
Self::I64(n) => u64::try_from(*n).ok(),
_ => None,
}
}
fn write_json<W: Write>(&self, writer: &mut W) -> io::Result<()> {
match self {
Self::Null => writer.write_all(b"null"),
Self::Bool(true) => writer.write_all(b"true"),
Self::Bool(false) => writer.write_all(b"false"),
Self::I64(n) => {
let mut buf = itoa::Buffer::new();
writer.write_all(buf.format(*n).as_bytes())
}
Self::U64(n) => {
let mut buf = itoa::Buffer::new();
writer.write_all(buf.format(*n).as_bytes())
}
Self::F64(n) => write!(writer, "{n}"),
Self::Borrowed(s) => write_json_string(writer, s),
Self::Raw(bytes) => writer.write_all(bytes),
}
}
}
impl TryFrom<String> for LogValue {
type Error = Errno;
fn try_from(s: String) -> Result<Self, Errno> {
Self::try_serialize(&s)
}
}
impl fmt::Display for LogValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => f.write_str("null"),
Self::Bool(true) => f.write_str("true"),
Self::Bool(false) => f.write_str("false"),
Self::I64(n) => write!(f, "{n}"),
Self::U64(n) => write!(f, "{n}"),
Self::F64(n) => write!(f, "{n}"),
Self::Borrowed(s) => write!(f, "\"{s}\""),
Self::Raw(bytes) => {
let s = std::str::from_utf8(bytes).unwrap_or("?");
f.write_str(s)
}
}
}
}
fn write_json_string<W: Write>(writer: &mut W, s: &str) -> io::Result<()> {
writer.write_all(b"\"")?;
for byte in s.as_bytes() {
match byte {
b'"' => writer.write_all(b"\\\"")?,
b'\\' => writer.write_all(b"\\\\")?,
b'\n' => writer.write_all(b"\\n")?,
b'\r' => writer.write_all(b"\\r")?,
b'\t' => writer.write_all(b"\\t")?,
0x00..=0x1f => {
let hi = b"0123456789abcdef"[(byte >> 4) as usize];
let lo = b"0123456789abcdef"[(byte & 0x0f) as usize];
writer.write_all(&[b'\\', b'u', b'0', b'0', hi, lo])?;
}
_ => writer.write_all(std::slice::from_ref(byte))?,
}
}
writer.write_all(b"\"")
}
impl From<i32> for LogValue {
fn from(v: i32) -> Self {
Self::I64(i64::from(v))
}
}
impl From<u32> for LogValue {
fn from(v: u32) -> Self {
Self::U64(u64::from(v))
}
}
impl From<i64> for LogValue {
fn from(v: i64) -> Self {
Self::I64(v)
}
}
impl From<u64> for LogValue {
fn from(v: u64) -> Self {
Self::U64(v)
}
}
impl From<bool> for LogValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<&'static str> for LogValue {
fn from(s: &'static str) -> Self {
Self::Borrowed(s)
}
}
impl Hash for LogValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Null => 0u8.hash(state),
Self::Bool(b) => {
1u8.hash(state);
b.hash(state);
}
Self::I64(n) => {
2u8.hash(state);
n.hash(state);
}
Self::U64(n) => {
2u8.hash(state);
n.hash(state);
}
Self::F64(n) => {
2u8.hash(state);
n.to_bits().hash(state);
}
Self::Borrowed(s) => {
3u8.hash(state);
s.hash(state);
}
Self::Raw(bytes) => {
3u8.hash(state);
bytes.hash(state);
}
}
}
}
#[derive(Clone)]
pub struct LogMap {
entries: Vec<(&'static str, LogValue)>,
}
impl Default for LogMap {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for LogMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.entries.iter().map(|(k, v)| (k, v)))
.finish()
}
}
impl LogMap {
pub fn new() -> Self {
LogMap {
entries: Vec::new(),
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn try_insert(&mut self, key: &'static str, value: LogValue) -> Result<(), Errno> {
if let Some(entry) = self.entries.iter_mut().find(|(k, _)| *k == key) {
entry.1 = value;
return Ok(());
}
self.entries.try_reserve(1).or(Err(Errno::ENOMEM))?;
self.entries.push((key, value));
Ok(())
}
pub fn try_shift_insert(
&mut self,
index: usize,
key: &'static str,
value: LogValue,
) -> Result<(), Errno> {
let existed = if let Some(pos) = self.entries.iter().position(|(k, _)| *k == key) {
self.entries.remove(pos);
true
} else {
false
};
let index = index.min(self.entries.len());
if !existed {
self.entries.try_reserve(1).or(Err(Errno::ENOMEM))?;
}
self.entries.insert(index, (key, value));
Ok(())
}
pub fn remove(&mut self, key: &str) -> Option<LogValue> {
if let Some(pos) = self.entries.iter().position(|(k, _)| *k == key) {
Some(self.entries.remove(pos).1)
} else {
None
}
}
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(&str, &LogValue) -> bool,
{
self.entries.retain(|(k, v)| f(k, v));
}
pub fn write_json<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(b"{")?;
for (idx, (key, val)) in self.entries.iter().enumerate() {
if idx > 0 {
writer.write_all(b",")?;
}
writer.write_all(b"\"")?;
writer.write_all(key.as_bytes())?;
writer.write_all(b"\":")?;
val.write_json(writer)?;
}
writer.write_all(b"}")
}
pub fn write_json_pretty<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(b"{\n")?;
let last = self.entries.len().saturating_sub(1);
for (idx, (key, val)) in self.entries.iter().enumerate() {
writer.write_all(b" \"")?;
writer.write_all(key.as_bytes())?;
writer.write_all(b"\": ")?;
val.write_json(writer)?;
if idx < last {
writer.write_all(b",")?;
}
writer.write_all(b"\n")?;
}
writer.write_all(b"}")
}
}
impl Hash for LogMap {
fn hash<H: Hasher>(&self, state: &mut H) {
const LOG_KEYS: &[&str] = &[
"act", "addr", "cap", "ctx", "err", "exe", "lib", "mnt", "mode", "msg", "op", "path",
"sig", "sys", "tip", "type", "unix",
];
for (key, val) in &self.entries {
if LOG_KEYS.binary_search(key).is_ok() {
key.hash(state);
val.hash(state);
}
}
}
}