#![doc = include_str!("../README.md")]
use std::fmt::Display;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use biometrics::Counter;
use tatl::{HeyListen, Stationary};
pub mod stdio;
#[cfg(feature = "prototk")]
pub mod protobuf;
pub const ALWAYS: u64 = 0;
pub const ERROR: u64 = 3;
pub const WARNING: u64 = 6;
pub const INFO: u64 = 9;
pub const DEBUG: u64 = 12;
pub const TRACING: u64 = 15;
static EMITTER_FAILURE: Counter = Counter::new("indicio.emitter_failure");
static EMITTER_FAILURE_MONITOR: Stationary =
Stationary::new("indicio.emitter_failure", &EMITTER_FAILURE);
pub fn register_biometrics(collector: biometrics::Collector) {
collector.register_counter(&EMITTER_FAILURE);
}
pub fn register_monitors(hey_listen: &mut HeyListen) {
hey_listen.register_stationary(&EMITTER_FAILURE_MONITOR);
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "prototk", derive(prototk_derive::Message))]
pub struct Values {
#[cfg_attr(feature = "prototk", prototk(1, message))]
values: Vec<Value>,
}
impl Deref for Values {
type Target = Vec<Value>;
fn deref(&self) -> &Vec<Value> {
&self.values
}
}
impl From<Vec<Value>> for Values {
fn from(values: Vec<Value>) -> Self {
Self { values }
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "prototk", derive(prototk_derive::Message))]
pub struct MapEntry {
#[cfg_attr(feature = "prototk", prototk(1, string))]
key: String,
#[cfg_attr(feature = "prototk", prototk(2, message))]
value: Value,
}
impl From<(String, Value)> for MapEntry {
fn from(entry: (String, Value)) -> Self {
Self {
key: entry.0,
value: entry.1,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "prototk", derive(prototk_derive::Message))]
pub struct Map {
#[cfg_attr(feature = "prototk", prototk(1, message))]
entries: Vec<MapEntry>,
}
impl Map {
pub fn insert(&mut self, key: String, value: Value) {
self.entries.push(MapEntry::from((key, value)));
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> + '_ {
self.entries.iter().map(|e| (e.key.as_str(), &e.value))
}
}
impl FromIterator<(String, Value)> for Map {
fn from_iter<T: IntoIterator<Item = (String, Value)>>(entries: T) -> Self {
Self {
entries: entries.into_iter().map(MapEntry::from).collect(),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "prototk", derive(prototk_derive::Message))]
pub enum Value {
#[cfg_attr(feature = "prototk", prototk(1, Bool))]
Bool(bool),
#[cfg_attr(feature = "prototk", prototk(2, uint64))]
U64(u64),
#[cfg_attr(feature = "prototk", prototk(3, sint64))]
I64(i64),
#[cfg_attr(feature = "prototk", prototk(4, double))]
F64(f64),
#[cfg_attr(feature = "prototk", prototk(5, string))]
String(String),
#[cfg_attr(feature = "prototk", prototk(6, message))]
Array(Values),
#[cfg_attr(feature = "prototk", prototk(7, message))]
Object(Map),
}
impl Value {
pub fn lookup(&self, key: &str) -> Option<&Value> {
if let Value::Object(map) = self {
map.entries
.iter()
.filter(|e| e.key == key)
.take(1)
.map(|e| &e.value)
.next()
} else {
None
}
}
}
impl Default for Value {
fn default() -> Self {
Self::Object(Map::default())
}
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Value::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
Value::U64(x) => write!(f, "{x}"),
Value::I64(x) => write!(f, "{x}"),
Value::F64(x) => write!(f, "{x}"),
Value::String(s) => {
pub fn escape(input: &str) -> String {
let mut out: Vec<char> = Vec::new();
for c in input.chars() {
if c == '\\' {
out.push('\\');
out.push('\\');
} else if c == '\n' {
out.push('\\');
out.push('n');
} else if c == '"' {
out.push('\\');
out.push('"');
} else {
out.push(c);
}
}
out.into_iter().collect()
}
write!(f, "\"{}\"", escape(s))
}
Value::Array(values) => {
let values = values.iter().map(|x| x.to_string()).collect::<Vec<_>>();
write!(f, "[{}]", values.join(", "))
}
Value::Object(values) => {
let values = values
.entries
.iter()
.map(|entry| format!("\"{}\": {}", entry.key, entry.value))
.collect::<Vec<_>>();
write!(f, "{{{}}}", values.join(", "))
}
}
}
}
impl From<char> for Value {
fn from(x: char) -> Self {
let mut s = String::new();
s.push(x);
Self::String(s)
}
}
impl From<bool> for Value {
fn from(x: bool) -> Self {
Self::Bool(x)
}
}
impl TryFrom<&Value> for bool {
type Error = ();
fn try_from(value: &Value) -> Result<Self, ()> {
if let Value::Bool(x) = value {
Ok(*x)
} else {
Err(())
}
}
}
impl From<i32> for Value {
fn from(x: i32) -> Self {
Self::I64(x as i64)
}
}
impl From<u32> for Value {
fn from(x: u32) -> Self {
Self::I64(x as i64)
}
}
impl From<i64> for Value {
fn from(x: i64) -> Self {
Self::I64(x)
}
}
impl TryFrom<&Value> for i64 {
type Error = ();
fn try_from(value: &Value) -> Result<Self, ()> {
if let Value::I64(x) = value {
Ok(*x)
} else {
Err(())
}
}
}
impl From<u64> for Value {
fn from(x: u64) -> Self {
Self::U64(x)
}
}
impl TryFrom<&Value> for u64 {
type Error = ();
fn try_from(value: &Value) -> Result<Self, ()> {
if let Value::U64(x) = value {
Ok(*x)
} else {
Err(())
}
}
}
impl From<usize> for Value {
fn from(x: usize) -> Self {
Self::U64(x as u64)
}
}
impl From<f64> for Value {
fn from(x: f64) -> Self {
Self::F64(x)
}
}
impl TryFrom<&Value> for f64 {
type Error = ();
fn try_from(value: &Value) -> Result<Self, ()> {
if let Value::F64(x) = value {
Ok(*x)
} else {
Err(())
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl TryFrom<&Value> for String {
type Error = ();
fn try_from(value: &Value) -> Result<Self, ()> {
if let Value::String(s) = value {
Ok(s.to_string())
} else {
Err(())
}
}
}
impl From<&String> for Value {
fn from(s: &String) -> Self {
Self::String(s.clone())
}
}
impl<V: Into<Value>> From<Vec<V>> for Value {
fn from(values: Vec<V>) -> Self {
let values = values.into_iter().map(|v| v.into()).collect();
Self::Array(Values { values })
}
}
impl Eq for Value {}
impl PartialEq for Value {
fn eq(&self, other: &Value) -> bool {
match (self, other) {
(Value::Bool(lhs), Value::Bool(rhs)) => lhs == rhs,
(Value::U64(lhs), Value::U64(rhs)) => lhs == rhs,
(Value::I64(lhs), Value::I64(rhs)) => lhs == rhs,
(Value::F64(lhs), Value::F64(rhs)) => lhs.total_cmp(rhs).is_eq(),
(Value::String(lhs), Value::String(rhs)) => lhs == rhs,
(Value::Array(lhs), Value::Array(rhs)) => lhs == rhs,
(Value::Object(lhs), Value::Object(rhs)) => lhs == rhs,
_ => false,
}
}
}
#[macro_export]
macro_rules! value {
($($value:tt)+) => {
$crate::value_internal!($($value)+)
};
}
#[macro_export]
macro_rules! value_internal {
(@array [$($elems:expr,)*]) => {
vec![$($elems,)*]
};
(@array [$($elems:expr),*]) => {
vec![$($elems),*]
};
(@array [$($elems:expr,)*] true $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!(true)] $($rem)*)
};
(@array [$($elems:expr,)*] false $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!(false)] $($rem)*)
};
(@array [$($elems:expr,)*] [$($array:tt)*] $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!([$($array)*])] $($rem)*)
};
(@array [$($elems:expr,)*] {$($obj:tt)*} $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!({$($obj)*})] $($rem)*)
};
(@array [$($elems:expr,)*] $next:expr, $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!($next),] $($rem)*)
};
(@array [$($elems:expr),*] , $($rem:tt)*) => {
$crate::value_internal!(@array [$($elems,)*] $($rem)*)
};
(@array [$($elems:expr,)*] $last:expr) => {
$crate::value_internal!(@array [$($elems,)* $crate::value_internal!($last),])
};
(@object $obj:ident () ()) => {};
(@object $obj:ident [$key:ident] ($value:expr) , $($rest:tt)*) => {
let _ = $obj.insert(stringify!($key).to_string(), $value);
$crate::value_internal!(@object $obj () ($($rest)*));
};
(@object $obj:ident [$key:ident] ($value:expr)) => {
let _ = $obj.insert(stringify!($key).to_string(), $value);
};
(@object $obj:ident ($key:ident) (: true $($rest:tt)*)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!(true)) $($rest)*);
};
(@object $obj:ident ($key:ident) (: false $($rest:tt)*)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!(false)) $($rest)*);
};
(@object $obj:ident ($key:ident) (: [$($array:tt)*] $($rest:tt)*)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!([$($array)*])) $($rest)*);
};
(@object $obj:ident ($key:ident) (: {$($map:tt)*} $($rest:tt)*)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!({$($map)*})) $($rest)*);
};
(@object $obj:ident ($key:ident) (: $value:expr , $($rest:tt)*)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!($value)) , $($rest)*);
};
(@object $obj:ident ($key:ident) (: $value:expr)) => {
$crate::value_internal!(@object $obj [$key] ($crate::value_internal!($value)));
};
(@object $obj:ident () ($key:ident $($rest:tt)*)) => {
$crate::value_internal!(@object $obj ($key) ($($rest)*));
};
(true) => {
$crate::Value::Bool(true)
};
(false) => {
$crate::Value::Bool(false)
};
([]) => {
$crate::Value::Array(vec![].into())
};
([ $($tt:tt)+ ]) => {
$crate::Value::Array($crate::value_internal!(@array [] $($tt)+).into())
};
({}) => {
$crate::Value::Object($crate::Map::default())
};
({ $($tt:tt)+ }) => {
$crate::Value::Object({
let mut values = $crate::Map::default();
$crate::value_internal!(@object values () ($($tt)+));
values
})
};
($val:expr) => {
$crate::Value::from($val)
};
}
pub trait Emitter: Send {
fn emit(&self, file: &str, line: u32, level: u64, value: Value);
fn flush(&self) {}
}
impl<E: Emitter + Sync> Emitter for Arc<E> {
fn emit(&self, file: &str, line: u32, level: u64, value: Value) {
<E as Emitter>::emit(self, file, line, level, value)
}
fn flush(&self) {
<E as Emitter>::flush(self)
}
}
pub struct LevelFilter<E: Emitter> {
pub level: u64,
pub wrap: E,
}
impl<E: Emitter> Emitter for LevelFilter<E> {
fn emit(&self, file: &str, line: u32, level: u64, value: Value) {
if level <= self.level {
self.wrap.emit(file, line, level, value);
}
}
fn flush(&self) {
self.wrap.flush()
}
}
macro_rules! impl_tuple_emitter {
($($name:ident)+) => {
#[allow(non_snake_case)]
impl<$($name: Emitter),+> Emitter for ($($name,)+) {
fn emit(&self, file: &str, line: u32, level: u64, value: Value) {
let ($(ref $name,)+) = *self;
$(<$name as Emitter>::emit($name, file, line, level, value.clone());)+
}
fn flush(&self) {
let ($(ref $name,)+) = *self;
$(<$name as Emitter>::flush($name);)+
}
}
};
}
impl_tuple_emitter! { A }
impl_tuple_emitter! { A B }
impl_tuple_emitter! { A B C }
impl_tuple_emitter! { A B C D }
impl_tuple_emitter! { A B C D E }
impl_tuple_emitter! { A B C D E F }
impl_tuple_emitter! { A B C D E F G }
impl_tuple_emitter! { A B C D E F G H }
impl_tuple_emitter! { A B C D E F G H I }
impl_tuple_emitter! { A B C D E F G H I J }
impl_tuple_emitter! { A B C D E F G H I J K }
impl_tuple_emitter! { A B C D E F G H I J K L }
impl_tuple_emitter! { A B C D E F G H I J K L M }
impl_tuple_emitter! { A B C D E F G H I J K L M N }
impl_tuple_emitter! { A B C D E F G H I J K L M N O }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U V }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U V W }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U V W X }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U V W X Y }
impl_tuple_emitter! { A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
pub struct Collector {
should_log: AtomicBool,
verbosity: AtomicU64,
emitter: Mutex<Option<Box<dyn Emitter>>>,
}
impl Collector {
pub const fn new() -> Self {
Self {
should_log: AtomicBool::new(false),
verbosity: AtomicU64::new(0),
emitter: Mutex::new(None),
}
}
pub fn is_logging(&self) -> bool {
self.should_log.load(Ordering::Relaxed)
}
pub fn verbosity(&self) -> u64 {
self.verbosity.load(Ordering::Relaxed)
}
pub fn set_verbosity(&self, verbosity: u64) {
self.verbosity.store(verbosity, Ordering::Relaxed);
}
pub fn emit(&self, file: &str, line: u32, level: u64, value: Value) {
if self.is_logging() && self.verbosity() >= level {
let mut emitter = self.emitter.lock().unwrap();
if let Some(emitter) = emitter.as_deref_mut() {
emitter.emit(file, line, level, value);
}
}
}
pub fn flush(&self) {
let mut emitter = self.emitter.lock().unwrap();
if let Some(emitter) = emitter.as_deref_mut() {
emitter.flush();
}
}
pub fn register<E: Emitter + 'static>(&self, emitter: E) {
let boxed: Box<dyn Emitter> = Box::new(emitter);
let mut emitter = self.emitter.lock().unwrap();
*emitter = Some(boxed);
self.should_log.store(true, Ordering::Relaxed);
}
pub fn deregister(&self) {
let mut emitter = self.emitter.lock().unwrap();
*emitter = None;
self.should_log.store(false, Ordering::Relaxed);
}
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "prototk", derive(prototk_derive::Message))]
pub struct Clue {
#[cfg_attr(feature = "prototk", prototk(1, string))]
pub file: String,
#[cfg_attr(feature = "prototk", prototk(2, uint32))]
pub line: u32,
#[cfg_attr(feature = "prototk", prototk(3, uint64))]
pub level: u64,
#[cfg_attr(feature = "prototk", prototk(4, uint64))]
pub timestamp: u64,
#[cfg_attr(feature = "prototk", prototk(5, message))]
pub value: Value,
}
impl Display for Clue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{}:{} {} {} {}",
self.file, self.line, self.level, self.timestamp, self.value
)
}
}
#[macro_export]
macro_rules! clue {
($collector:path, $level:expr, { $($value:tt)* }) => {
if $collector.is_logging() && $collector.verbosity() >= $level {
let loc = concat!(module_path!(), " ", file!());
$collector.emit(loc, line!(), $level, $crate::value!({ $($value)* }));
}
};
}
#[macro_export]
macro_rules! puzzle_piece {
($type_name:ident { $($tt:tt)* }) => {
puzzle_piece!(@struct [$type_name] {} ($($tt)*));
impl $type_name {
pub fn extract(value: &indicio::Value) -> Option<Self> {
let mut this = Self::default();
puzzle_piece!(@extract this value [] ($($tt)*));
Some(this)
}
}
};
(@struct [$type_name:ident] { $($field:ident: $ty:path,)* } ()) => {
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct $type_name {
$($field: $ty,)*
}
};
(@struct [$type_name:ident] { $($field:ident: $ty:path,)* } ($new_field:ident: $new_ty:path, $($tt:tt)*)) => {
puzzle_piece!(@struct [$type_name] { $($field: $ty,)* $new_field: $new_ty, } ($($tt)*));
};
(@struct [$type_name:ident] { $($field:ident: $ty:path,)* } ($new_field:ident: { $($tt1:tt)* })) => {
puzzle_piece!(@struct [$type_name] { $($field: $ty,)* } ($($tt1)*));
};
(@struct [$type_name:ident] { $($field:ident: $ty:path,)* } ($new_field:ident: { $($tt1:tt)* },)) => {
puzzle_piece!(@struct [$type_name] { $($field: $ty,)* } ($($tt1)*));
};
(@struct [$type_name:ident] { $($field:ident: $ty:path,)* } ($new_field:ident: { $($tt1:tt)* }, $($tt2:tt)*)) => {
puzzle_piece!(@struct [$type_name] { $($field: $ty,)* } ($($tt1)* $($tt2)*));
};
(@extract $this:ident $value:ident [] ()) => {
};
(@extract $this:ident $value:ident [$($key:ident)*] $new_field:ident) => {
let v = $value;
$(let v = v.lookup(stringify!($key))?;)*
$this.$new_field = v.try_into().ok()?;
};
(@extract $this:ident $value:ident [$($key:ident)*] ($new_field:ident: $new_ty:path,)) => {
puzzle_piece!(@extract $this $value [$($key)* $new_field] $new_field);
};
(@extract $this:ident $value:ident [$($key:ident)*] ($new_field:ident: $new_ty:path, $($tt:tt)*)) => {
puzzle_piece!(@extract $this $value [$($key)* $new_field] $new_field);
puzzle_piece!(@extract $this $value [$($key)*] ($($tt)*));
};
(@extract $this:ident $value:ident [$($key:ident)*] ($new_field:ident: { $($tt1:tt)* }, $($tt2:tt)*)) => {
puzzle_piece!(@extract $this $value [$($key)* $new_field] ($($tt1)*));
puzzle_piece!(@extract $this $value [$($key)*] ($($tt2)*));
};
(@extract $this:ident $value:ident [$($key:ident)*] ($new_field:ident: { $($tt1:tt)* })) => {
puzzle_piece!(@extract $this $value [$($key)* $new_field] ($($tt)*));
};
(@extract $this:ident $value:ident [$($key:ident)*] ($new_field:ident: { $($tt1:tt)* },)) => {
puzzle_piece!(@extract $this $value [$($key)* $new_field] ($($tt)*));
};
}
#[cfg(test)]
mod tests {
#![allow(clippy::excessive_precision)]
#![allow(clippy::approx_constant)]
use super::*;
#[test]
fn value_bool() {
assert_eq!(Value::Bool(true), value!(true));
assert_eq!(Value::Bool(false), value!(false));
}
#[test]
fn value_i64() {
assert_eq!(Value::I64(i64::MIN), value!(-9223372036854775808i64));
assert_eq!(Value::I64(i64::MAX), value!(9223372036854775807i64));
}
#[test]
fn value_u64() {
assert_eq!(Value::U64(u64::MAX), value!(18446744073709551615u64));
}
#[test]
fn value_f64() {
assert_eq!(
Value::F64(std::f64::consts::PI),
value!(3.14159265358979323846264338327950288_f64)
);
}
#[test]
fn value_string() {
assert_eq!(Value::String("foo".to_string()), value!("foo"));
}
#[test]
fn value_array() {
assert_eq!(Value::Array(vec![].into()), value!([]));
assert_eq!(
Value::Array(
vec![
Value::Bool(false),
Value::Bool(true),
Value::Array(
vec![
Value::String("hello".to_string()),
Value::String("world".to_string())
]
.into()
)
]
.into()
),
value!([false, true, ["hello", "world"]])
);
}
#[test]
fn value_object() {
assert_eq!(Value::Object(vec![].into_iter().collect()), value!({}));
assert_eq!(
Value::Object(
vec![
("hello".to_string(), Value::String("world".to_string())),
(
"consts".to_string(),
Value::Array(
vec![Value::F64(2.718281828459045), Value::F64(3.141592653589793)]
.into()
)
),
(
"recursive".to_string(),
Value::Object(
vec![
("hello".to_string(), Value::String("world".to_string())),
(
"consts".to_string(),
Value::Array(
vec![
Value::F64(2.718281828459045),
Value::F64(3.141592653589793)
]
.into()
)
),
]
.into_iter()
.collect()
)
),
]
.into_iter()
.collect()
),
value!({
hello: "world",
consts: [
2.71828182845904523536028747135266250_f64,
3.14159265358979323846264338327950288_f64,
],
recursive: {
hello: "world",
consts: [
2.71828182845904523536028747135266250_f64,
3.14159265358979323846264338327950288_f64,
],
}
})
);
}
}