Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "trc"
version = "0.16.22"
edition = "2024"
[dependencies]
event_macro = { path = "./event-macro" }
mail-auth = { version = "0.13", features = ["arc"] }
mail-parser = { version = "0.11", features = ["full_encoding"] }
base64 = "0.23.1"
serde = "1.0"
serde_json = "1.0.151"
reqwest = { version = "0.13", default-features = false, features = ["rustls", "http2"]}
rtrb = "0.4.0"
parking_lot = "0.12.5"
tokio = { version = "1.53", features = ["net", "macros"] }
ahash = "0.8.12"
rkyv = { version = "0.8.18", features = ["little_endian"] }
compact_str = "0.10.0"
hashify = "0.2.9"
[features]
test_mode = []
dev_mode = []
enterprise = []
[dev-dependencies]
[lints]
workspace = true
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "event_macro"
version = "0.16.22"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
syn = { version = "3.0", features = ["full"] }
quote = "1.0"
+220
View File
@@ -0,0 +1,220 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Expr, ExprPath, Ident, Token, parse::Parse, parse_macro_input};
static mut GLOBAL_ID_COUNTER: usize = 0;
#[proc_macro_attribute]
pub fn key_names(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let enum_variants = match &input.data {
Data::Enum(data_enum) => &data_enum.variants,
_ => panic!("This macro only works with enums"),
};
let mut variant_names = Vec::new();
let mut camel_case_names = Vec::new();
let mut snake_case_names = Vec::new();
for variant in enum_variants.iter() {
let variant_name = &variant.ident;
variant_names.push(variant_name);
snake_case_names.push(to_snake_case(&variant_name.to_string()));
camel_case_names.push(
variant_name
.to_string()
.char_indices()
.map(|(i, c)| if i == 0 { c.to_ascii_lowercase() } else { c })
.collect::<String>(),
);
}
let id_fn = quote! {
pub fn id(&self) -> &'static str {
match self {
#(Self::#variant_names => #snake_case_names,)*
}
}
};
let name_fn = quote! {
pub fn name(&self) -> &'static str {
match self {
#(Self::#variant_names => #camel_case_names,)*
}
}
};
let parse_fn = quote! {
pub fn try_parse(name: &str) -> Option<Self> {
match name {
#(#camel_case_names => Some(Self::#variant_names),)*
_ => None,
}
}
};
let expanded = quote! {
#input
impl #name {
#name_fn
#id_fn
#parse_fn
}
};
TokenStream::from(expanded)
}
#[proc_macro]
pub fn total_event_count(_item: TokenStream) -> TokenStream {
let count = unsafe { GLOBAL_ID_COUNTER };
let expanded = quote! {
#count
};
TokenStream::from(expanded)
}
fn to_snake_case(name: &str) -> String {
let mut out = String::with_capacity(name.len());
for (idx, ch) in name.char_indices() {
if ch.is_ascii_uppercase() {
if idx > 0 {
out.push('-');
}
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
struct EventMacroInput {
event: Ident,
param: Expr,
key_values: Vec<(Ident, Expr)>,
}
impl Parse for EventMacroInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let event: Ident = input.parse()?;
let content;
syn::parenthesized!(content in input);
let param: Expr = content.parse()?;
let mut key_values = Vec::new();
while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
let key: Ident = input.parse()?;
input.parse::<Token![=]>()?;
let value: Expr = input.parse()?;
key_values.push((key, value));
}
Ok(EventMacroInput {
event,
param,
key_values,
})
}
}
#[proc_macro]
pub fn event(input: TokenStream) -> TokenStream {
let EventMacroInput {
event,
param,
key_values,
} = parse_macro_input!(input as EventMacroInput);
let key_value_tokens = key_values.iter().map(|(key, value)| {
quote! {
(trc::Key::#key, trc::Value::from(#value))
}
});
// This avoids having to evaluate expensive values when we know we are not interested in the event
let key_value_metric_tokens = key_values.iter().filter_map(|(key, value)| {
if key.is_metric_key() {
Some(quote! {
(trc::Key::#key, trc::Value::from(#value))
})
} else {
None
}
});
let expanded = if matches!(&param, Expr::Path(ExprPath { path, .. }) if path.segments.len() > 1 && path.segments.last().unwrap().arguments.is_empty() )
{
quote! {{
const ET: trc::EventType = trc::EventType::#event(#param);
const ET_ID: usize = ET.to_id() as usize;
if trc::Collector::has_interest(ET_ID) {
let keys = vec![#(#key_value_tokens),*];
if trc::Collector::is_metric(ET_ID) {
trc::Collector::record_metric(ET, ET_ID, &keys);
}
trc::Event::with_keys(ET, keys).send();
} else if trc::Collector::is_metric(ET_ID) {
trc::Collector::record_metric(ET, ET_ID, &[#(#key_value_metric_tokens),*]);
}
}}
} else {
quote! {{
let et = trc::EventType::#event(#param);
let et_id = et.to_id() as usize;
if trc::Collector::has_interest(et_id) {
let keys = vec![#(#key_value_tokens),*];
if trc::Collector::is_metric(et_id) {
trc::Collector::record_metric(et, et_id, &keys);
}
trc::Event::with_keys(et, keys).send();
} else if trc::Collector::is_metric(et_id) {
trc::Collector::record_metric(et, et_id, &[#(#key_value_metric_tokens),*]);
}
}}
};
TokenStream::from(expanded)
}
trait IsMetricKey {
fn is_metric_key(&self) -> bool;
}
impl IsMetricKey for Ident {
fn is_metric_key(&self) -> bool {
matches!(
self.to_string().as_ref(),
"Total"
| "Elapsed"
| "Size"
| "TotalSuccesses"
| "TotalFailures"
| "DmarcPass"
| "DmarcQuarantine"
| "DmarcReject"
| "DmarcNone"
| "DkimPass"
| "DkimFail"
| "DkimNone"
| "SpfPass"
| "SpfFail"
| "SpfNone"
| "Protocol"
| "Code"
)
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
pub struct AtomicU32Array<const N: usize>([AtomicU32; N]);
pub struct AtomicU64Array<const N: usize>([AtomicU64; N]);
impl<const N: usize> AtomicU32Array<N> {
#[allow(clippy::new_without_default)]
#[allow(clippy::declare_interior_mutable_const)]
pub const fn new() -> Self {
Self({
const INIT: AtomicU32 = AtomicU32::new(0);
let mut array = [INIT; N];
let mut i = 0;
while i < N {
array[i] = AtomicU32::new(0);
i += 1;
}
array
})
}
#[inline(always)]
pub fn get(&self, index: usize) -> u32 {
self.0[index].load(Ordering::Relaxed)
}
#[inline(always)]
pub fn set(&self, index: usize, value: u32) {
self.0[index].store(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn add(&self, index: usize, value: u32) {
self.0[index].fetch_add(value, Ordering::Relaxed);
}
pub fn inner(&self) -> &[AtomicU32; N] {
&self.0
}
}
impl<const N: usize> AtomicU64Array<N> {
#[allow(clippy::new_without_default)]
#[allow(clippy::declare_interior_mutable_const)]
pub const fn new() -> Self {
Self({
const INIT: AtomicU64 = AtomicU64::new(0);
let mut array = [INIT; N];
let mut i = 0;
while i < N {
array[i] = AtomicU64::new(0);
i += 1;
}
array
})
}
#[inline(always)]
pub fn get(&self, index: usize) -> u64 {
self.0[index].load(Ordering::Relaxed)
}
#[inline(always)]
pub fn set(&self, index: usize, value: u64) {
self.0[index].store(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn add(&self, index: usize, value: u64) {
self.0[index].fetch_add(value, Ordering::Relaxed);
}
pub fn inner(&self) -> &[AtomicU64; N] {
&self.0
}
}
+140
View File
@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::ipc::{USIZE_BITS, USIZE_BITS_MASK, bitset::Bitset};
pub struct AtomicBitset<const N: usize>([AtomicUsize; N]);
impl<const N: usize> AtomicBitset<N> {
#[allow(clippy::new_without_default)]
#[allow(clippy::declare_interior_mutable_const)]
pub const fn new() -> Self {
Self({
const INIT: AtomicUsize = AtomicUsize::new(0);
let mut array = [INIT; N];
let mut i = 0;
while i < N {
array[i] = AtomicUsize::new(0);
i += 1;
}
array
})
}
#[inline(always)]
pub fn set(&self, index: impl Into<usize>) {
let index = index.into();
self.0[index / USIZE_BITS].fetch_or(1 << (index & USIZE_BITS_MASK), Ordering::Relaxed);
}
#[inline(always)]
pub fn clear(&self, index: impl Into<usize>) {
let index = index.into();
self.0[index / USIZE_BITS].fetch_and(!(1 << (index & USIZE_BITS_MASK)), Ordering::Relaxed);
}
#[inline(always)]
pub fn get(&self, index: impl Into<usize>) -> bool {
let index = index.into();
self.0[index / USIZE_BITS].load(Ordering::Relaxed) & (1 << (index & USIZE_BITS_MASK)) != 0
}
pub fn update(&self, bitset: impl AsRef<Bitset<N>>) {
let bitset = bitset.as_ref();
for i in 0..N {
self.0[i].store(bitset.0[i], Ordering::Relaxed);
}
}
pub fn union(&self, bitset: impl AsRef<Bitset<N>>) {
let bitset = bitset.as_ref();
for i in 0..N {
self.0[i].fetch_or(bitset.0[i], Ordering::Relaxed);
}
}
pub fn clear_all(&self) {
for i in 0..N {
self.0[i].store(0, Ordering::Relaxed);
}
}
pub fn is_empty(&self) -> bool {
for i in 0..N {
if self.0[i].load(Ordering::Relaxed) != 0 {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_SIZE: usize = 1000;
type TestBitset = AtomicBitset<{ TEST_SIZE.div_ceil(USIZE_BITS) }>;
static BITSET: TestBitset = TestBitset::new();
#[test]
fn test_atomic_bitset() {
for i in 0..TEST_SIZE {
assert!(!BITSET.get(i), "Bit {} should be unset in new BITSET", i);
}
for i in 0..TEST_SIZE {
assert!(!BITSET.get(i), "Bit {} should be initially unset", i);
BITSET.set(i);
assert!(BITSET.get(i), "Bit {} should be set after setting", i);
}
BITSET.clear_all();
for i in 0..TEST_SIZE {
BITSET.set(i);
assert!(BITSET.get(i), "Bit {} should be set before clearing", i);
BITSET.clear(i);
assert!(!BITSET.get(i), "Bit {} should be unset after clearing", i);
}
BITSET.clear_all();
// Set even bits
for i in (0..TEST_SIZE).step_by(2) {
BITSET.set(i);
}
// Check all bits
for i in 0..TEST_SIZE {
if i % 2 == 0 {
assert!(BITSET.get(i), "Even bit {} should be set", i);
} else {
assert!(!BITSET.get(i), "Odd bit {} should be unset", i);
}
}
// Clear even bits and set odd bits
for i in 0..TEST_SIZE {
if i % 2 == 0 {
BITSET.clear(i);
} else {
BITSET.set(i);
}
}
// Check all bits again
for i in 0..TEST_SIZE {
if i % 2 == 0 {
assert!(!BITSET.get(i), "Even bit {} should now be unset", i);
} else {
assert!(BITSET.get(i), "Odd bit {} should now be set", i);
}
}
}
}
+66
View File
@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::{AtomicU64, Ordering};
pub struct AtomicCounter {
id: &'static str,
description: &'static str,
unit: &'static str,
value: AtomicU64,
}
impl AtomicCounter {
pub const fn new(id: &'static str, description: &'static str, unit: &'static str) -> Self {
Self {
id,
description,
unit,
value: AtomicU64::new(0),
}
}
#[inline(always)]
pub fn increment(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
#[inline(always)]
pub fn increment_by(&self, value: u64) {
self.value.fetch_add(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn decrement(&self) {
self.value.fetch_sub(1, Ordering::Relaxed);
}
#[inline(always)]
pub fn decrement_by(&self, value: u64) {
self.value.fetch_sub(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn get(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn id(&self) -> &'static str {
self.id
}
pub fn description(&self) -> &'static str {
self.description
}
pub fn unit(&self) -> &'static str {
self.unit
}
pub fn is_active(&self) -> bool {
self.value.load(Ordering::Relaxed) > 0
}
}
+57
View File
@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::{AtomicU64, Ordering};
use crate::MetricType;
pub struct AtomicGauge {
id: MetricType,
value: AtomicU64,
}
impl AtomicGauge {
pub const fn new(id: MetricType) -> Self {
Self {
id,
value: AtomicU64::new(0),
}
}
#[inline(always)]
pub fn increment(&self) {
self.value.fetch_add(1, Ordering::Relaxed);
}
#[inline(always)]
pub fn set(&self, value: u64) {
self.value.store(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn decrement(&self) {
self.value.fetch_sub(1, Ordering::Relaxed);
}
#[inline(always)]
pub fn get(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn add(&self, value: u64) {
self.value.fetch_add(value, Ordering::Relaxed);
}
#[inline(always)]
pub fn subtract(&self, value: u64) {
self.value.fetch_sub(value, Ordering::Relaxed);
}
pub fn id(&self) -> MetricType {
self.id
}
}
+198
View File
@@ -0,0 +1,198 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::{AtomicU64, Ordering};
use crate::MetricType;
use super::array::AtomicU32Array;
pub struct AtomicHistogram<const N: usize> {
id: MetricType,
buckets: AtomicU32Array<N>,
upper_bounds: [u64; N],
sum: AtomicU64,
count: AtomicU64,
min: AtomicU64,
max: AtomicU64,
}
impl<const N: usize> AtomicHistogram<N> {
pub const fn new(id: MetricType, upper_bounds: [u64; N]) -> Self {
Self {
buckets: AtomicU32Array::new(),
upper_bounds,
sum: AtomicU64::new(0),
count: AtomicU64::new(0),
min: AtomicU64::new(u64::MAX),
max: AtomicU64::new(0),
id,
}
}
pub fn observe(&self, value: u64) {
self.sum.fetch_add(value, Ordering::Relaxed);
self.count.fetch_add(1, Ordering::Relaxed);
self.min.fetch_min(value, Ordering::Relaxed);
self.max.fetch_max(value, Ordering::Relaxed);
for (idx, upper_bound) in self.upper_bounds.iter().enumerate() {
if value < *upper_bound {
self.buckets.add(idx, 1);
return;
}
}
unreachable!()
}
pub fn id(&self) -> MetricType {
self.id
}
pub fn sum(&self) -> u64 {
self.sum.load(Ordering::Relaxed)
}
pub fn count(&self) -> u64 {
self.count.load(Ordering::Relaxed)
}
pub fn average(&self) -> f64 {
let sum = self.sum();
let count = self.count();
if count > 0 {
sum as f64 / count as f64
} else {
0.0
}
}
pub fn min(&self) -> Option<u64> {
let min = self.min.load(Ordering::Relaxed);
if min != u64::MAX { Some(min) } else { None }
}
pub fn max(&self) -> Option<u64> {
let max = self.max.load(Ordering::Relaxed);
if max != 0 { Some(max) } else { None }
}
pub fn buckets_iter(&self) -> impl IntoIterator<Item = u64> + '_ {
self.buckets
.inner()
.iter()
.map(|bucket| bucket.load(Ordering::Relaxed) as u64)
}
pub fn buckets_vec(&self) -> Vec<u64> {
let mut vec = Vec::with_capacity(N);
for bucket in self.buckets.inner().iter() {
vec.push(bucket.load(Ordering::Relaxed) as u64);
}
vec
}
pub fn buckets_len(&self) -> usize {
N
}
pub fn upper_bounds_iter(&self) -> impl IntoIterator<Item = u64> + '_ {
self.upper_bounds.iter().copied()
}
pub fn upper_bounds_vec(&self) -> Vec<f64> {
let mut vec = Vec::with_capacity(N - 1);
for upper_bound in self.upper_bounds.iter().take(N - 1) {
vec.push(*upper_bound as f64);
}
vec
}
pub fn is_active(&self) -> bool {
self.count.load(Ordering::Relaxed) > 0
}
pub const fn new_message_sizes(id: MetricType) -> AtomicHistogram<12> {
AtomicHistogram::new(
id,
[
500, // 500 bytes
1_000, // 1 KB
10_000, // 10 KB
100_000, // 100 KB
1_000_000, // 1 MB
5_000_000, // 5 MB
10_000_000, // 10 MB
25_000_000, // 25 MB
50_000_000, // 50 MB
100_000_000, // 100 MB
500_000_000, // 500 MB
u64::MAX, // Catch-all for any larger sizes
],
)
}
pub const fn new_short_durations(id: MetricType) -> AtomicHistogram<12> {
AtomicHistogram::new(
id,
[
5, // 5 milliseconds
10, // 10 milliseconds
50, // 50 milliseconds
100, // 100 milliseconds
500, // 0.5 seconds
1_000, // 1 second
2_000, // 2 seconds
5_000, // 5 seconds
10_000, // 10 seconds
30_000, // 30 seconds
60_000, // 1 minute
u64::MAX, // Catch-all for any longer durations
],
)
}
pub const fn new_medium_durations(id: MetricType) -> AtomicHistogram<12> {
AtomicHistogram::new(
id,
[
250,
500,
1_000,
5_000,
10_000, // For quick connections (seconds)
60_000,
(60 * 5) * 1_000,
(60 * 10) * 1_000,
(60 * 30) * 1_000, // For medium-length connections (minutes)
(60 * 60) * 1_000,
(60 * 60 * 5) * 1_000,
u64::MAX, // For extreme cases (8 hours and 1 day)
],
)
}
pub const fn new_long_durations(id: MetricType) -> AtomicHistogram<12> {
AtomicHistogram::new(
id,
[
1_000, // 1 second
30_000, // 30 seconds
300_000, // 5 minutes
600_000, // 10 minutes
1_800_000, // 30 minutes
3_600_000, // 1 hour
14_400_000, // 5 hours
28_800_000, // 8 hours
43_200_000, // 12 hours
86_400_000, // 1 day
604_800_000, // 1 week
u64::MAX, // Catch-all for any longer durations
],
)
}
}
+11
View File
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod array;
pub mod bitset;
pub mod counter;
pub mod gauge;
pub mod histogram;
+598
View File
@@ -0,0 +1,598 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{borrow::Cow, fmt::Debug, str::FromStr, time::Duration};
use compact_str::{CompactString, ToCompactString, format_compact};
use mail_auth::common::verify::VerifySignature;
use crate::*;
impl AsRef<EventType> for Error {
fn as_ref(&self) -> &EventType {
&self.0.inner
}
}
impl From<&'static str> for Value {
fn from(value: &'static str) -> Self {
Self::String(CompactString::const_new(value))
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::String(CompactString::from_string_buffer(value))
}
}
impl From<CompactString> for Value {
fn from(value: CompactString) -> Self {
Self::String(value)
}
}
impl From<Box<str>> for Value {
fn from(value: Box<str>) -> Self {
Self::String(CompactString::from(value))
}
}
impl From<u64> for Value {
fn from(value: u64) -> Self {
Self::UInt(value)
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Self::Int(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<f32> for Value {
fn from(value: f32) -> Self {
Self::Float(value.into())
}
}
impl From<u16> for Value {
fn from(value: u16) -> Self {
Self::UInt(value.into())
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Self::Int(value.into())
}
}
impl From<u32> for Value {
fn from(value: u32) -> Self {
Self::UInt(value.into())
}
}
impl From<usize> for Value {
fn from(value: usize) -> Self {
Self::UInt(value as u64)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<IpAddr> for Value {
fn from(value: IpAddr) -> Self {
match value {
IpAddr::V4(ip) => Value::Ipv4(ip),
IpAddr::V6(ip) => Value::Ipv6(ip),
}
}
}
impl<T: Into<Value>> From<Option<T>> for Value {
fn from(value: Option<T>) -> Self {
match value {
Some(value) => value.into(),
None => Self::None,
}
}
}
impl From<Duration> for Value {
fn from(value: Duration) -> Self {
Self::Duration(value.as_millis() as u64)
}
}
impl From<Error> for Value {
fn from(value: Error) -> Self {
Self::Event(value)
}
}
impl From<EventType> for Error {
fn from(value: EventType) -> Self {
Error::new(value)
}
}
impl From<StoreEvent> for Error {
fn from(value: StoreEvent) -> Self {
Error::new(EventType::Store(value))
}
}
impl From<AuthEvent> for Error {
fn from(value: AuthEvent) -> Self {
Error::new(EventType::Auth(value))
}
}
impl From<Vec<u8>> for Value {
fn from(value: Vec<u8>) -> Self {
Self::Bytes(value)
}
}
impl From<&[u8]> for Value {
fn from(value: &[u8]) -> Self {
Self::Bytes(value.to_vec())
}
}
impl From<Cow<'static, str>> for Value {
fn from(value: Cow<'static, str>) -> Self {
match value {
Cow::Borrowed(value) => Self::String(CompactString::const_new(value)),
Cow::Owned(value) => Self::String(value.into()),
}
}
}
impl<T> From<&crate::Result<T>> for Value
where
T: Debug,
{
fn from(value: &crate::Result<T>) -> Self {
match value {
Ok(value) => format_compact!("{:?}", value).into(),
Err(err) => Value::Event(err.clone()),
}
}
}
impl<T> From<Vec<T>> for Value
where
T: Into<Value>,
{
fn from(value: Vec<T>) -> Self {
Self::Array(value.into_iter().map(Into::into).collect())
}
}
impl<T> From<&[T]> for Value
where
T: Into<Value> + Clone,
{
fn from(value: &[T]) -> Self {
Self::Array(value.iter().map(|v| v.clone().into()).collect())
}
}
impl EventType {
pub fn from_io_error(self, err: std::io::Error) -> Error {
self.reason(err).details("I/O error")
}
pub fn from_json_error(self, err: serde_json::Error) -> Error {
self.reason(err).details("JSON deserialization failed")
}
pub fn from_base64_error(self, err: base64::DecodeError) -> Error {
self.reason(err).details("Base64 decoding failed")
}
pub fn from_http_error(self, err: reqwest::Error) -> Error {
self.into_err()
.ctx_opt(
Key::Url,
err.url().map(|url| url.as_ref().to_compact_string()),
)
.ctx_opt(Key::Code, err.status().map(|status| status.as_u16()))
.reason(err)
}
pub fn from_http_str_error(self, err: reqwest::header::ToStrError) -> Error {
self.reason(err)
.details("Failed to convert header to string")
}
}
impl From<mail_auth::Error> for Error {
fn from(err: mail_auth::Error) -> Self {
match err {
mail_auth::Error::ParseError => {
EventType::MailAuth(MailAuthEvent::ParseError).into_err()
}
mail_auth::Error::MissingParameters => {
EventType::MailAuth(MailAuthEvent::MissingParameters).into_err()
}
mail_auth::Error::NoHeadersFound => {
EventType::MailAuth(MailAuthEvent::NoHeadersFound).into_err()
}
mail_auth::Error::Io(details) => EventType::MailAuth(MailAuthEvent::Io)
.into_err()
.details(CompactString::from(details)),
mail_auth::Error::Base64 => EventType::MailAuth(MailAuthEvent::Base64).into_err(),
mail_auth::Error::NotAligned => {
EventType::MailAuth(MailAuthEvent::PolicyNotAligned).into_err()
}
mail_auth::Error::Crypto(err) => match err {
mail_auth::common::crypto::CryptoError::Library(details) => {
EventType::MailAuth(MailAuthEvent::Crypto)
.into_err()
.details(CompactString::from(details))
}
mail_auth::common::crypto::CryptoError::FailedVerification => {
EventType::Dkim(DkimEvent::FailedVerification).into_err()
}
mail_auth::common::crypto::CryptoError::IncompatibleAlgorithms => {
EventType::Dkim(DkimEvent::IncompatibleAlgorithms).into_err()
}
},
mail_auth::Error::Dns(err) => match err {
mail_auth::DnsError::Resolver(details) => {
EventType::MailAuth(MailAuthEvent::DnsError)
.into_err()
.details(CompactString::from(details))
}
mail_auth::DnsError::RecordNotFound(code) => {
EventType::MailAuth(MailAuthEvent::DnsRecordNotFound)
.into_err()
.code(code.to_str())
}
mail_auth::DnsError::InvalidRecordType => {
EventType::MailAuth(MailAuthEvent::DnsInvalidRecordType).into_err()
}
},
mail_auth::Error::Dkim(err) => match err {
mail_auth::dkim::DkimError::UnsupportedVersion => {
EventType::Dkim(DkimEvent::UnsupportedVersion).into_err()
}
mail_auth::dkim::DkimError::UnsupportedAlgorithm => {
EventType::Dkim(DkimEvent::UnsupportedAlgorithm).into_err()
}
mail_auth::dkim::DkimError::UnsupportedCanonicalization => {
EventType::Dkim(DkimEvent::UnsupportedCanonicalization).into_err()
}
mail_auth::dkim::DkimError::UnsupportedKeyType => {
EventType::Dkim(DkimEvent::UnsupportedKeyType).into_err()
}
mail_auth::dkim::DkimError::FailedBodyHashMatch => {
EventType::Dkim(DkimEvent::FailedBodyHashMatch).into_err()
}
mail_auth::dkim::DkimError::FailedAuidMatch => {
EventType::Dkim(DkimEvent::FailedAuidMatch).into_err()
}
mail_auth::dkim::DkimError::RevokedPublicKey => {
EventType::Dkim(DkimEvent::RevokedPublicKey).into_err()
}
mail_auth::dkim::DkimError::SignatureExpired => {
EventType::Dkim(DkimEvent::SignatureExpired).into_err()
}
mail_auth::dkim::DkimError::SignatureLength => {
EventType::Dkim(DkimEvent::SignatureLength).into_err()
}
},
mail_auth::Error::Arc(err) => match err {
mail_auth::arc::ArcError::ChainTooLong => {
EventType::Arc(ArcEvent::ChainTooLong).into_err()
}
mail_auth::arc::ArcError::InvalidInstance(instance) => {
EventType::Arc(ArcEvent::InvalidInstance).ctx(Key::Id, instance)
}
mail_auth::arc::ArcError::InvalidCV => {
EventType::Arc(ArcEvent::InvalidCv).into_err()
}
mail_auth::arc::ArcError::HasHeaderTag => {
EventType::Arc(ArcEvent::HasHeaderTag).into_err()
}
mail_auth::arc::ArcError::BrokenChain => {
EventType::Arc(ArcEvent::BrokenChain).into_err()
}
mail_auth::arc::ArcError::FailedBodyHashMatch => {
EventType::Dkim(DkimEvent::FailedBodyHashMatch).into_err()
}
mail_auth::arc::ArcError::SignatureExpired => {
EventType::Dkim(DkimEvent::SignatureExpired).into_err()
}
mail_auth::arc::ArcError::SignatureLength => {
EventType::Dkim(DkimEvent::SignatureLength).into_err()
}
},
mail_auth::Error::Dkim2(err) => match err {
mail_auth::dkim2::Dkim2Error::InstanceMissing(m) => {
EventType::Dkim(DkimEvent::InstanceMissing).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::InstanceSyntax(m) => {
EventType::Dkim(DkimEvent::InstanceSyntax).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::InstanceTagMissing { m, tag } => {
EventType::Dkim(DkimEvent::InstanceTagMissing)
.ctx(Key::Id, m)
.details(tag)
}
mail_auth::dkim2::Dkim2Error::InstanceNotSigned(m) => {
EventType::Dkim(DkimEvent::InstanceNotSigned).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::InstanceAboveSignature(m) => {
EventType::Dkim(DkimEvent::InstanceAboveSignature).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::SignatureMissing(i) => {
EventType::Dkim(DkimEvent::SignatureMissing).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::SignatureSyntax(i) => {
EventType::Dkim(DkimEvent::SignatureSyntax).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::SignatureTagMissing { i, tag } => {
EventType::Dkim(DkimEvent::SignatureTagMissing)
.ctx(Key::Id, i)
.details(tag)
}
mail_auth::dkim2::Dkim2Error::SignatureTagUnexpected { i, tag } => {
EventType::Dkim(DkimEvent::SignatureTagUnexpected)
.ctx(Key::Id, i)
.details(tag)
}
mail_auth::dkim2::Dkim2Error::SequenceGap => {
EventType::Dkim(DkimEvent::SequenceGap).into_err()
}
mail_auth::dkim2::Dkim2Error::SequenceOverflow => {
EventType::Dkim(DkimEvent::SequenceOverflow).into_err()
}
mail_auth::dkim2::Dkim2Error::ChainTooLong => {
EventType::Dkim(DkimEvent::ChainTooLong).into_err()
}
mail_auth::dkim2::Dkim2Error::SignatureExpired(i) => {
EventType::Dkim(DkimEvent::SignatureExpired).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::MailFromMismatch(i) => {
EventType::Dkim(DkimEvent::MailFromMismatch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::RcptToMismatch(i) => {
EventType::Dkim(DkimEvent::RcptToMismatch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::MailFromDomainMismatch(i) => {
EventType::Dkim(DkimEvent::MailFromDomainMismatch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::NextDomainMismatch(i) => {
EventType::Dkim(DkimEvent::NextDomainMismatch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::CustodyBreak(i) => {
EventType::Dkim(DkimEvent::CustodyBreak).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeyFetch(i) => {
EventType::Dkim(DkimEvent::PublicKeyFetch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeyMissing(i) => {
EventType::Dkim(DkimEvent::PublicKeyMissing).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeyMultiple(i) => {
EventType::Dkim(DkimEvent::PublicKeyMultiple).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeySyntax(i) => {
EventType::Dkim(DkimEvent::PublicKeySyntax).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeyAlgorithmMismatch(i) => {
EventType::Dkim(DkimEvent::PublicKeyAlgorithmMismatch).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::PublicKeyRevoked(i) => {
EventType::Dkim(DkimEvent::RevokedPublicKey).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::IncorrectSignature(i) => {
EventType::Dkim(DkimEvent::FailedVerification).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::NoValidAlgorithm(i) => {
EventType::Dkim(DkimEvent::NoValidAlgorithm).ctx(Key::Id, i)
}
mail_auth::dkim2::Dkim2Error::HeaderHashMismatch(m) => {
EventType::Dkim(DkimEvent::HeaderHashMismatch).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::BodyHashMismatch(m) => {
EventType::Dkim(DkimEvent::FailedBodyHashMatch).ctx(Key::Id, m)
}
mail_auth::dkim2::Dkim2Error::Modified => {
EventType::Dkim(DkimEvent::Modified).into_err()
}
mail_auth::dkim2::Dkim2Error::Exploded => {
EventType::Dkim(DkimEvent::Exploded).into_err()
}
},
}
}
}
impl From<&mail_auth::DkimResult> for Error {
fn from(value: &mail_auth::DkimResult) -> Self {
match value.clone() {
mail_auth::DkimResult::Pass => Error::new(EventType::Dkim(DkimEvent::Pass)),
mail_auth::DkimResult::Neutral(err) => {
Error::new(EventType::Dkim(DkimEvent::Neutral)).caused_by(Error::from(err))
}
mail_auth::DkimResult::Fail(err) => {
Error::new(EventType::Dkim(DkimEvent::Fail)).caused_by(Error::from(err))
}
mail_auth::DkimResult::PermError(err) => {
Error::new(EventType::Dkim(DkimEvent::PermError)).caused_by(Error::from(err))
}
mail_auth::DkimResult::TempError(err) => {
Error::new(EventType::Dkim(DkimEvent::TempError)).caused_by(Error::from(err))
}
mail_auth::DkimResult::None => Error::new(EventType::Dkim(DkimEvent::None)),
}
}
}
impl From<&mail_auth::Dkim2Result> for Error {
fn from(value: &mail_auth::Dkim2Result) -> Self {
match value.clone() {
mail_auth::Dkim2Result::Pass => Error::new(EventType::Dkim(DkimEvent::Pass)),
mail_auth::Dkim2Result::Fail(err) => {
Error::new(EventType::Dkim(DkimEvent::Fail)).caused_by(Error::from(err))
}
mail_auth::Dkim2Result::PermError(err) => {
Error::new(EventType::Dkim(DkimEvent::PermError)).caused_by(Error::from(err))
}
mail_auth::Dkim2Result::TempError(err) => {
Error::new(EventType::Dkim(DkimEvent::TempError)).caused_by(Error::from(err))
}
mail_auth::Dkim2Result::None => Error::new(EventType::Dkim(DkimEvent::None)),
}
}
}
impl From<&mail_auth::dkim2::Dkim2Output<'_>> for Error {
fn from(value: &mail_auth::dkim2::Dkim2Output<'_>) -> Self {
Error::from(value.result()).ctx_opt(
Key::Domain,
value
.chain()
.first()
.map(|link| link.signature.d.to_compact_string()),
)
}
}
impl From<&mail_auth::DmarcResult> for Error {
fn from(value: &mail_auth::DmarcResult) -> Self {
match value.clone() {
mail_auth::DmarcResult::Pass => Error::new(EventType::Dmarc(DmarcEvent::Pass)),
mail_auth::DmarcResult::Fail(err) => {
Error::new(EventType::Dmarc(DmarcEvent::Fail)).caused_by(Error::from(err))
}
mail_auth::DmarcResult::PermError(err) => {
Error::new(EventType::Dmarc(DmarcEvent::PermError)).caused_by(Error::from(err))
}
mail_auth::DmarcResult::TempError(err) => {
Error::new(EventType::Dmarc(DmarcEvent::TempError)).caused_by(Error::from(err))
}
mail_auth::DmarcResult::None => Error::new(EventType::Dmarc(DmarcEvent::None)),
}
}
}
impl From<&mail_auth::DkimOutput<'_>> for Error {
fn from(value: &mail_auth::DkimOutput<'_>) -> Self {
Error::from(value.result()).ctx_opt(
Key::Domain,
value.signature().map(|s| s.domain().to_compact_string()),
)
}
}
impl From<&mail_auth::IprevOutput> for Error {
fn from(value: &mail_auth::IprevOutput) -> Self {
match value.result().clone() {
mail_auth::IprevResult::Pass => Error::new(EventType::Iprev(IprevEvent::Pass)),
mail_auth::IprevResult::Fail(err) => {
Error::new(EventType::Iprev(IprevEvent::Fail)).caused_by(Error::from(err))
}
mail_auth::IprevResult::PermError(err) => {
Error::new(EventType::Iprev(IprevEvent::PermError)).caused_by(Error::from(err))
}
mail_auth::IprevResult::TempError(err) => {
Error::new(EventType::Iprev(IprevEvent::TempError)).caused_by(Error::from(err))
}
mail_auth::IprevResult::None => Error::new(EventType::Iprev(IprevEvent::None)),
}
.ctx_opt(
Key::Details,
value.ptr.as_ref().map(|s| {
s.iter()
.map(|v| Value::String(v.as_ref().into()))
.collect::<Vec<_>>()
}),
)
}
}
impl From<&mail_auth::SpfOutput> for Error {
fn from(value: &mail_auth::SpfOutput) -> Self {
Error::new(EventType::Spf(match value.result() {
mail_auth::SpfResult::Pass => SpfEvent::Pass,
mail_auth::SpfResult::Fail => SpfEvent::Fail,
mail_auth::SpfResult::SoftFail => SpfEvent::SoftFail,
mail_auth::SpfResult::Neutral => SpfEvent::Neutral,
mail_auth::SpfResult::PermError => SpfEvent::PermError,
mail_auth::SpfResult::TempError => SpfEvent::TempError,
mail_auth::SpfResult::None => SpfEvent::None,
}))
.ctx_opt(
Key::Details,
value.explanation().map(|s| s.to_compact_string()),
)
}
}
impl From<rkyv::rancor::Error> for Error {
fn from(value: rkyv::rancor::Error) -> Self {
Error::new(EventType::Store(StoreEvent::DeserializeError))
.reason(value)
.details("Rkyv de/serialization failed")
}
}
pub trait AssertSuccess
where
Self: Sized,
{
fn assert_success(
self,
cause: EventType,
) -> impl std::future::Future<Output = crate::Result<Self>> + Send;
}
impl AssertSuccess for reqwest::Response {
async fn assert_success(self, cause: EventType) -> crate::Result<Self> {
let status = self.status();
if status.is_success() {
Ok(self)
} else {
Err(cause
.ctx(Key::Code, status.as_u16())
.details("HTTP request failed")
.ctx_opt(Key::Reason, self.text().await.map(CompactString::from).ok()))
}
}
}
impl FromStr for EventType {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
EventType::parse(s).ok_or(())
}
}
impl FromStr for Key {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Key::parse(s).ok_or(())
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::Level;
use std::{cmp::Ordering, fmt::Display, str::FromStr};
impl PartialOrd for Level {
#[inline(always)]
fn partial_cmp(&self, other: &Level) -> Option<Ordering> {
Some(self.cmp(other))
}
#[inline(always)]
fn lt(&self, other: &Level) -> bool {
(*other as usize) < (*self as usize)
}
#[inline(always)]
fn le(&self, other: &Level) -> bool {
(*other as usize) <= (*self as usize)
}
#[inline(always)]
fn gt(&self, other: &Level) -> bool {
(*other as usize) > (*self as usize)
}
#[inline(always)]
fn ge(&self, other: &Level) -> bool {
(*other as usize) >= (*self as usize)
}
}
impl Ord for Level {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
(*other as usize).cmp(&(*self as usize))
}
}
impl FromStr for Level {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"disable" => Ok(Self::Disable),
"trace" => Ok(Self::Trace),
"debug" => Ok(Self::Debug),
"info" => Ok(Self::Info),
"warn" => Ok(Self::Warn),
"error" => Ok(Self::Error),
_ => Err(s.to_string()),
}
}
}
impl Level {
pub fn as_str(&self) -> &'static str {
match self {
Self::Disable => "DISABLE",
Self::Trace => "TRACE",
Self::Debug => "DEBUG",
Self::Info => "INFO",
Self::Warn => "WARN",
Self::Error => "ERROR",
}
}
pub fn is_contained(&self, other: Self) -> bool {
*self >= other && other != Level::Disable && *self != Level::Disable
}
}
impl Display for Level {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
+683
View File
@@ -0,0 +1,683 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod conv;
pub mod level;
pub mod enums;
#[allow(clippy::match_like_matches_macro)]
pub mod enums_impl;
use compact_str::ToCompactString;
use std::fmt::Display;
use crate::*;
impl<T> Event<T> {
pub fn with_capacity(inner: T, capacity: usize) -> Self {
Self {
inner,
keys: Vec::with_capacity(capacity),
}
}
pub fn with_keys(inner: T, keys: Vec<(Key, Value)>) -> Self {
Self { inner, keys }
}
pub fn new(inner: T) -> Self {
Self {
inner,
keys: Vec::with_capacity(5),
}
}
pub fn value(&self, key: Key) -> Option<&Value> {
self.keys
.iter()
.find_map(|(k, v)| if *k == key { Some(v) } else { None })
}
pub fn value_as_str(&self, key: Key) -> Option<&str> {
self.value(key).and_then(|v| v.as_str())
}
pub fn value_as_uint(&self, key: Key) -> Option<u64> {
self.value(key).and_then(|v| v.to_uint())
}
pub fn take_value(&mut self, key: Key) -> Option<Value> {
self.keys.iter_mut().find_map(|(k, v)| {
if *k == key {
Some(std::mem::take(v))
} else {
None
}
})
}
pub fn into_boxed(self) -> Box<Self> {
Box::new(self)
}
}
impl Error {
#[inline(always)]
pub fn new(inner: EventType) -> Self {
Error(Box::new(Event::new(inner)))
}
#[inline(always)]
pub fn set_ctx(&mut self, key: Key, value: impl Into<Value>) {
self.0.keys.push((key, value.into()));
}
#[inline(always)]
pub fn ctx(mut self, key: Key, value: impl Into<Value>) -> Self {
self.0.keys.push((key, value.into()));
self
}
#[inline(always)]
pub fn ctx_unique(mut self, key: Key, value: impl Into<Value>) -> Self {
if self.0.keys.iter().all(|(k, _)| *k != key) {
self.0.keys.push((key, value.into()));
}
self
}
#[inline(always)]
pub fn ctx_opt(self, key: Key, value: Option<impl Into<Value>>) -> Self {
match value {
Some(value) => self.ctx(key, value),
None => self,
}
}
#[inline(always)]
pub fn matches(&self, inner: EventType) -> bool {
self.0.inner == inner
}
#[inline(always)]
pub fn event_type(&self) -> EventType {
self.0.inner
}
#[inline(always)]
pub fn span_id(self, session_id: u64) -> Self {
self.ctx(Key::SpanId, session_id)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Self {
self.ctx(Key::CausedBy, error)
}
#[inline(always)]
pub fn details(self, error: impl Into<Value>) -> Self {
self.ctx(Key::Details, error)
}
#[inline(always)]
pub fn code(self, error: impl Into<Value>) -> Self {
self.ctx(Key::Code, error)
}
#[inline(always)]
pub fn id(self, error: impl Into<Value>) -> Self {
self.ctx(Key::Id, error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Self {
self.ctx(Key::Reason, error.to_compact_string())
}
#[inline(always)]
pub fn document_id(self, id: u32) -> Self {
self.ctx(Key::DocumentId, id)
}
#[inline(always)]
pub fn account_id(self, id: u32) -> Self {
self.ctx(Key::AccountId, id)
}
#[inline(always)]
pub fn collection(self, id: impl Into<u8>) -> Self {
self.ctx(Key::Collection, id.into() as u64)
}
#[inline(always)]
pub fn wrap(self, cause: EventType) -> Self {
Error::new(cause).caused_by(self)
}
#[inline(always)]
pub fn keys(&self) -> &[(Key, Value)] {
&self.0.keys
}
#[inline(always)]
pub fn value(&self, key: Key) -> Option<&Value> {
self.0.value(key)
}
#[inline(always)]
pub fn value_as_str(&self, key: Key) -> Option<&str> {
self.0.value_as_str(key)
}
#[inline(always)]
pub fn value_as_uint(&self, key: Key) -> Option<u64> {
self.0.value_as_uint(key)
}
#[inline(always)]
pub fn take_value(&mut self, key: Key) -> Option<Value> {
self.0.take_value(key)
}
#[inline(always)]
pub fn is_assertion_failure(&self) -> bool {
self.0.inner == EventType::Store(StoreEvent::AssertValueFailed)
}
pub fn key(&self, key: Key) -> Option<&Value> {
self.0
.keys
.iter()
.find_map(|(k, v)| if *k == key { Some(v) } else { None })
}
#[inline(always)]
pub fn is_jmap_method_error(&self) -> bool {
!matches!(
self.0.inner,
EventType::Jmap(
JmapEvent::UnknownCapability | JmapEvent::NotJson | JmapEvent::NotRequest
)
)
}
#[inline(always)]
pub fn must_disconnect(&self) -> bool {
matches!(
self.0.inner,
EventType::Network(_)
| EventType::Auth(AuthEvent::TooManyAttempts)
| EventType::Limit(LimitEvent::ConcurrentRequest | LimitEvent::TooManyRequests)
| EventType::Security(_)
)
}
#[inline(always)]
pub fn should_write_err(&self) -> bool {
!matches!(self.0.inner, EventType::Network(_) | EventType::Security(_))
}
pub fn corrupted_key(key: &[u8], value: Option<&[u8]>, caused_by: &'static str) -> Error {
EventType::Store(StoreEvent::DataCorruption)
.ctx(Key::Key, key)
.ctx_opt(Key::Value, value)
.ctx(Key::CausedBy, caused_by)
}
}
impl Event<EventDetails> {
pub fn span_id(&self) -> Option<u64> {
for (key, value) in &self.keys {
match (key, value) {
(Key::SpanId, Value::UInt(value)) => return Some(*value),
(Key::SpanId, Value::Int(value)) => return Some(*value as u64),
_ => {}
}
}
None
}
}
impl EventType {
#[inline(always)]
pub fn is_span_start(&self) -> bool {
matches!(
self,
EventType::Smtp(SmtpEvent::ConnectionStart)
| EventType::Imap(ImapEvent::ConnectionStart)
| EventType::ManageSieve(ManageSieveEvent::ConnectionStart)
| EventType::Pop3(Pop3Event::ConnectionStart)
| EventType::Http(HttpEvent::ConnectionStart)
| EventType::Delivery(DeliveryEvent::AttemptStart)
)
}
#[inline(always)]
pub fn is_span_end(&self) -> bool {
matches!(
self,
EventType::Smtp(SmtpEvent::ConnectionEnd)
| EventType::Imap(ImapEvent::ConnectionEnd)
| EventType::ManageSieve(ManageSieveEvent::ConnectionEnd)
| EventType::Pop3(Pop3Event::ConnectionEnd)
| EventType::Http(HttpEvent::ConnectionEnd)
| EventType::Delivery(DeliveryEvent::AttemptEnd)
)
}
pub fn is_raw_io(&self) -> bool {
matches!(
self,
EventType::Imap(ImapEvent::RawInput | ImapEvent::RawOutput)
| EventType::Smtp(SmtpEvent::RawInput | SmtpEvent::RawOutput)
| EventType::Pop3(Pop3Event::RawInput | Pop3Event::RawOutput)
| EventType::ManageSieve(ManageSieveEvent::RawInput | ManageSieveEvent::RawOutput)
| EventType::Delivery(DeliveryEvent::RawInput | DeliveryEvent::RawOutput)
| EventType::Milter(MilterEvent::Read | MilterEvent::Write)
)
}
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(self)
}
}
impl StoreEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Store(self))
}
}
impl DnsEvent {
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Dns(self))
}
}
impl AcmeEvent {
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Acme(self))
}
}
impl DkimEvent {
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Dkim(self))
}
}
impl SecurityEvent {
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Security(self))
}
}
impl AuthEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Auth(self))
}
}
impl JmapEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Jmap(self))
}
}
impl LimitEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Limit(self))
}
}
impl ResourceEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Resource(self))
}
}
impl SmtpEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Smtp(self))
}
}
impl SieveEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Sieve(self))
}
}
impl SpamEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Spam(self))
}
}
impl ImapEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Imap(self))
}
#[inline(always)]
pub fn caused_by(self, error: impl Into<Value>) -> Error {
self.into_err().caused_by(error)
}
#[inline(always)]
pub fn reason(self, error: impl Display) -> Error {
self.into_err().reason(error)
}
}
impl Pop3Event {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Pop3(self))
}
}
impl ManageSieveEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::ManageSieve(self))
}
}
impl NetworkEvent {
#[inline(always)]
pub fn ctx(self, key: Key, value: impl Into<Value>) -> Error {
self.into_err().ctx(key, value)
}
#[inline(always)]
pub fn into_err(self) -> Error {
Error::new(EventType::Network(self))
}
}
impl Value {
pub fn from_maybe_string(value: &[u8]) -> Self {
if let Ok(value) = std::str::from_utf8(value) {
Self::String(value.into())
} else {
Self::Bytes(value.to_vec())
}
}
pub fn to_uint(&self) -> Option<u64> {
match self {
Self::UInt(value) => Some(*value),
Self::Int(value) => Some(*value as u64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(value) => Some(value.as_str()),
_ => None,
}
}
pub fn into_string(self) -> Option<CompactString> {
match self {
Self::String(value) => Some(value),
_ => None,
}
}
}
impl<T> AddContext<T> for Result<T> {
#[inline(always)]
fn caused_by(self, location: &'static str) -> Result<T> {
match self {
Ok(value) => Ok(value),
Err(mut err) => {
err.set_ctx(Key::CausedBy, location);
Err(err)
}
}
}
#[inline(always)]
fn add_context<F>(self, f: F) -> Result<T>
where
F: FnOnce(Error) -> Error,
{
match self {
Ok(value) => Ok(value),
Err(err) => Err(f(err)),
}
}
}
impl std::error::Error for Error {}
impl Eq for Error {}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
if self.0.inner == other.0.inner && self.0.keys.len() == other.0.keys.len() {
for kv in self.0.keys.iter() {
if !other.0.keys.iter().any(|okv| kv == okv) {
return false;
}
}
true
} else {
false
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::String(l0), Self::String(r0)) => l0 == r0,
(Self::UInt(l0), Self::UInt(r0)) => l0 == r0,
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
(Self::Bytes(l0), Self::Bytes(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Ipv4(l0), Self::Ipv4(r0)) => l0 == r0,
(Self::Ipv6(l0), Self::Ipv6(r0)) => l0 == r0,
(Self::Event(l0), Self::Event(r0)) => l0 == r0,
(Self::Array(l0), Self::Array(r0)) => l0 == r0,
_ => false,
}
}
}
impl Eq for Value {}
impl From<EventType> for usize {
fn from(value: EventType) -> Self {
value.to_id() as usize
}
}
impl AsRef<Event<EventDetails>> for Event<EventDetails> {
fn as_ref(&self) -> &Event<EventDetails> {
self
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{USIZE_BITS, USIZE_BITS_MASK};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Bitset<const N: usize>(pub(crate) [usize; N]);
impl<const N: usize> Bitset<N> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self([0; N])
}
pub const fn all() -> Self {
Self([usize::MAX; N])
}
#[inline(always)]
pub fn set(&mut self, index: impl Into<usize>) {
let index = index.into();
self.0[index / USIZE_BITS] |= 1 << (index & USIZE_BITS_MASK);
}
#[inline(always)]
pub fn clear(&mut self, index: impl Into<usize>) {
let index = index.into();
self.0[index / USIZE_BITS] &= !(1 << (index & USIZE_BITS_MASK));
}
#[inline(always)]
pub fn get(&self, index: impl Into<usize>) -> bool {
let index = index.into();
self.0[index / USIZE_BITS] & (1 << (index & USIZE_BITS_MASK)) != 0
}
pub fn union(&mut self, other: &Self) {
for i in 0..N {
self.0[i] |= other.0[i];
}
}
pub fn intersection(&mut self, other: &Self) {
for i in 0..N {
self.0[i] &= other.0[i];
}
}
pub fn difference(&mut self, other: &Self) {
for i in 0..N {
self.0[i] &= !other.0[i];
}
}
pub fn clear_many(&mut self, other: &Self) {
for i in 0..N {
self.0[i] &= !other.0[i];
}
}
pub fn clear_all(&mut self) {
for i in 0..N {
self.0[i] = 0;
}
}
pub fn is_empty(&self) -> bool {
for i in 0..N {
if self.0[i] != 0 {
return false;
}
}
true
}
pub fn inner(&self) -> &[usize; N] {
&self.0
}
}
impl<const N: usize> Default for Bitset<N> {
fn default() -> Self {
Self::new()
}
}
+121
View File
@@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{
cell::UnsafeCell,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use rtrb::{Consumer, Producer, PushError, RingBuffer};
use crate::{
Error, Event, EventType,
ipc::collector::{COLLECTOR_THREAD, COLLECTOR_UPDATES, Update},
};
use super::collector::{Collector, CollectorThread};
pub(crate) static CHANNEL_FLAGS: AtomicU64 = AtomicU64::new(0);
pub(crate) const CHANNEL_SIZE: usize = 10240;
pub(crate) const CHANNEL_UPDATE_MARKER: u64 = 1 << 63;
thread_local! {
static EVENT_TX: UnsafeCell<Sender> = {
// Create channel.
let (tx, rx) = RingBuffer::new(CHANNEL_SIZE);
// Register receiver with collector.
COLLECTOR_UPDATES.lock().push(Update::RegisterReceiver { receiver: Receiver { rx } });
// Spawn collector thread.
let collector = COLLECTOR_THREAD.clone();
CHANNEL_FLAGS.fetch_or(CHANNEL_UPDATE_MARKER, Ordering::Relaxed);
collector.thread().unpark();
// Return sender.
UnsafeCell::new(Sender {
tx,
collector,
overflow: Vec::with_capacity(0),
})
};
}
pub struct Sender {
tx: Producer<Event<EventType>>,
collector: Arc<CollectorThread>,
overflow: Vec<Event<EventType>>,
}
pub struct Receiver {
rx: Consumer<Event<EventType>>,
}
#[derive(Debug)]
pub struct ChannelError;
impl Sender {
pub fn send(&mut self, event: Event<EventType>) -> Result<(), ChannelError> {
while let Some(event) = self.overflow.pop() {
if let Err(PushError::Full(event)) = self.tx.push(event) {
self.overflow.push(event);
break;
}
}
if let Err(PushError::Full(event)) = self.tx.push(event) {
if self.overflow.len() <= CHANNEL_SIZE * 2 {
self.overflow.push(event);
} else {
return Err(ChannelError);
}
}
Ok(())
}
}
impl Receiver {
pub fn try_recv(&mut self) -> Result<Option<Event<EventType>>, ChannelError> {
match self.rx.pop() {
Ok(event) => Ok(Some(event)),
Err(_) => {
if !self.rx.is_abandoned() {
Ok(None)
} else {
Err(ChannelError)
}
}
}
}
}
impl Event<EventType> {
pub fn send(self) {
// SAFETY: EVENT_TX is thread-local.
let _ = EVENT_TX.try_with(|tx| unsafe {
let tx = &mut *tx.get();
if tx.send(self).is_ok() {
CHANNEL_FLAGS.fetch_add(1, Ordering::Relaxed);
tx.collector.thread().unpark();
}
});
}
pub fn send_with_metrics(self) {
Collector::record_metric(self.inner, self.inner.to_id() as usize, &self.keys);
self.send();
}
}
impl Error {
pub fn send(self) {
self.0.send();
}
}
+360
View File
@@ -0,0 +1,360 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{
sync::{Arc, LazyLock, atomic::Ordering},
thread::{Builder, JoinHandle, park},
time::SystemTime,
};
use ahash::AHashMap;
use atomics::bitset::AtomicBitset;
use ipc::{
USIZE_BITS,
channel::{CHANNEL_FLAGS, CHANNEL_UPDATE_MARKER, Receiver},
subscriber::{Interests, Subscriber},
};
use parking_lot::Mutex;
use crate::*;
pub(crate) type GlobalInterests = AtomicBitset<{ TOTAL_EVENT_COUNT.div_ceil(USIZE_BITS) }>;
pub(crate) static TRACE_INTERESTS: GlobalInterests = GlobalInterests::new();
pub(crate) type CollectorThread = JoinHandle<()>;
pub(crate) static ACTIVE_SUBSCRIBERS: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub(crate) static COLLECTOR_UPDATES: Mutex<Vec<Update>> = Mutex::new(Vec::new());
pub(crate) static EVENT_TYPES: &[EventType] = EventType::variants();
#[allow(clippy::enum_variant_names)]
pub(crate) enum Update {
RegisterReceiver {
receiver: Receiver,
},
RegisterSubscriber {
subscriber: Subscriber,
},
UnregisterSubscriber {
id: String,
},
UpdateSubscriber {
id: String,
interests: Interests,
lossy: bool,
},
UpdateLevels {
levels: AHashMap<EventType, Level>,
},
Shutdown,
}
pub struct Collector {
receivers: Vec<Receiver>,
subscribers: Vec<Subscriber>,
levels: [Level; TOTAL_EVENT_COUNT],
active_spans: AHashMap<u64, Arc<Event<EventDetails>>>,
}
const HTTP_CONN_START: usize = EventType::Http(HttpEvent::ConnectionStart).to_id() as usize;
const HTTP_CONN_END: usize = EventType::Http(HttpEvent::ConnectionEnd).to_id() as usize;
const IMAP_CONN_START: usize = EventType::Imap(ImapEvent::ConnectionStart).to_id() as usize;
const IMAP_CONN_END: usize = EventType::Imap(ImapEvent::ConnectionEnd).to_id() as usize;
const POP3_CONN_START: usize = EventType::Pop3(Pop3Event::ConnectionStart).to_id() as usize;
const POP3_CONN_END: usize = EventType::Pop3(Pop3Event::ConnectionEnd).to_id() as usize;
const SMTP_CONN_START: usize = EventType::Smtp(SmtpEvent::ConnectionStart).to_id() as usize;
const SMTP_CONN_END: usize = EventType::Smtp(SmtpEvent::ConnectionEnd).to_id() as usize;
const MANAGE_SIEVE_CONN_START: usize =
EventType::ManageSieve(ManageSieveEvent::ConnectionStart).to_id() as usize;
const MANAGE_SIEVE_CONN_END: usize =
EventType::ManageSieve(ManageSieveEvent::ConnectionEnd).to_id() as usize;
const EV_ATTEMPT_START: usize = EventType::Delivery(DeliveryEvent::AttemptStart).to_id() as usize;
const EV_ATTEMPT_END: usize = EventType::Delivery(DeliveryEvent::AttemptEnd).to_id() as usize;
const STALE_SPAN_CHECK_WATERMARK: usize = 8000;
const SPAN_MAX_HOLD: u64 = 60 * 60 * 24; // 1 day
pub(crate) static COLLECTOR_THREAD: LazyLock<Arc<CollectorThread>> = LazyLock::new(|| {
Arc::new(
Builder::new()
.name("stalwart-collector".to_string())
.spawn(move || {
Collector::default().collect();
})
.expect("Failed to start event collector"),
)
});
impl Collector {
fn collect(&mut self) {
let mut do_continue = true;
// Update
self.update();
while do_continue {
match CHANNEL_FLAGS.swap(0, Ordering::Relaxed) {
0 => {
park();
}
CHANNEL_UPDATE_MARKER..=u64::MAX => {
do_continue = self.update();
}
_ => {}
}
// Collect all events
let mut closed_rxs = Vec::new();
for (rx_idx, rx) in self.receivers.iter_mut().enumerate() {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
loop {
match rx.try_recv() {
Ok(Some(event)) => {
// Build event
let event_id = event.inner.to_id() as usize;
let mut event = Event {
inner: EventDetails {
level: self.levels[event_id],
typ: event.inner,
timestamp,
span: None,
},
keys: event.keys,
};
// Track spans
let event = match event_id {
HTTP_CONN_START
| IMAP_CONN_START
| POP3_CONN_START
| SMTP_CONN_START
| MANAGE_SIEVE_CONN_START
| EV_ATTEMPT_START => {
let event = Arc::new(event);
self.active_spans.insert(
event.span_id().unwrap_or_else(|| {
panic!("Missing span ID: {event:?}")
}),
event.clone(),
);
if self.active_spans.len() > STALE_SPAN_CHECK_WATERMARK {
self.active_spans.retain(|_, span| {
timestamp.saturating_sub(span.inner.timestamp)
< SPAN_MAX_HOLD
});
}
event
}
HTTP_CONN_END
| IMAP_CONN_END
| POP3_CONN_END
| SMTP_CONN_END
| MANAGE_SIEVE_CONN_END
| EV_ATTEMPT_END => {
if let Some(span) = self
.active_spans
.remove(&event.span_id().expect("Missing span ID"))
{
event.inner.span = Some(span.clone());
} else {
#[cfg(any(feature = "dev_mode", feature = "test_mode"))]
{
if event.span_id().unwrap() != 0 {
eprintln!("Unregistered span ID: {event:?}");
}
}
}
Arc::new(event)
}
_ => {
if let Some(span_id) = event.span_id() {
if let Some(span) = self.active_spans.get(&span_id) {
event.inner.span = Some(span.clone());
} else {
#[cfg(any(
feature = "dev_mode",
feature = "test_mode"
))]
{
if span_id != 0 {
eprintln!("Unregistered span ID: {event:?}");
}
}
}
}
Arc::new(event)
}
};
// Send to subscribers
for subscriber in self.subscribers.iter_mut() {
subscriber.push_event(event_id, event.clone());
}
}
Ok(None) => {
break;
}
Err(_) => {
closed_rxs.push(rx_idx); // Channel is closed, remove.
break;
}
}
}
}
if do_continue {
// Remove closed receivers (should be rare in Tokio)
if !closed_rxs.is_empty() {
let mut receivers = Vec::with_capacity(self.receivers.len() - closed_rxs.len());
for (rx_idx, rx) in self.receivers.drain(..).enumerate() {
if !closed_rxs.contains(&rx_idx) {
receivers.push(rx);
}
}
self.receivers = receivers;
}
// Send batched events
if !self.subscribers.is_empty() {
self.subscribers
.retain_mut(|subscriber| subscriber.send_batch().is_ok());
}
}
}
// Send remaining events
for mut subscriber in self.subscribers.drain(..) {
let _ = subscriber.send_batch();
}
}
fn update(&mut self) -> bool {
for update in COLLECTOR_UPDATES.lock().drain(..) {
match update {
Update::RegisterReceiver { receiver } => {
self.receivers.push(receiver);
}
Update::RegisterSubscriber { subscriber } => {
ACTIVE_SUBSCRIBERS.lock().push(subscriber.id.clone());
self.subscribers.push(subscriber);
}
Update::UnregisterSubscriber { id } => {
ACTIVE_SUBSCRIBERS.lock().retain(|s| s != &id);
self.subscribers.retain(|s| s.id != id);
}
Update::UpdateSubscriber {
id,
interests,
lossy,
} => {
for subscriber in self.subscribers.iter_mut() {
if subscriber.id == id {
subscriber.interests = interests;
subscriber.lossy = lossy;
break;
}
}
}
Update::UpdateLevels { levels } => {
for event in EVENT_TYPES.iter() {
let event_id = event.to_id() as usize;
if let Some(level) = levels.get(event) {
self.levels[event_id] = *level;
} else {
self.levels[event_id] = event.level();
}
}
}
Update::Shutdown => return false,
}
}
true
}
pub fn set_interests(mut interests: Interests) {
if !interests.is_empty() {
for event_type in EVENT_TYPES.iter() {
if event_type.is_span_start() || event_type.is_span_end() {
interests.set(*event_type);
}
}
}
TRACE_INTERESTS.update(interests);
}
pub fn union_interests(interests: Interests) {
TRACE_INTERESTS.union(interests);
}
#[inline(always)]
pub fn has_interest(event: impl Into<usize>) -> bool {
TRACE_INTERESTS.get(event)
}
pub fn get_subscribers() -> Vec<String> {
ACTIVE_SUBSCRIBERS.lock().clone()
}
pub fn update_custom_levels(levels: AHashMap<EventType, Level>) {
COLLECTOR_UPDATES
.lock()
.push(Update::UpdateLevels { levels });
}
pub fn update_subscriber(id: String, interests: Interests, lossy: bool) {
COLLECTOR_UPDATES.lock().push(Update::UpdateSubscriber {
id,
interests,
lossy,
});
}
pub fn remove_subscriber(id: String) {
COLLECTOR_UPDATES
.lock()
.push(Update::UnregisterSubscriber { id });
}
pub fn shutdown() {
COLLECTOR_UPDATES.lock().push(Update::Shutdown);
Collector::reload();
}
pub fn is_enabled() -> bool {
!TRACE_INTERESTS.is_empty()
}
pub fn reload() {
CHANNEL_FLAGS.fetch_or(CHANNEL_UPDATE_MARKER, Ordering::Relaxed);
COLLECTOR_THREAD.thread().unpark();
}
}
impl Default for Collector {
fn default() -> Self {
let mut c = Collector {
subscribers: Vec::new(),
levels: [Level::Disable; TOTAL_EVENT_COUNT],
active_spans: AHashMap::new(),
receivers: Vec::new(),
};
for event in EVENT_TYPES.iter() {
let event_id = event.to_id() as usize;
c.levels[event_id] = event.level();
}
c
}
}
+415
View File
@@ -0,0 +1,415 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::atomic::Ordering;
use atomics::{array::AtomicU32Array, gauge::AtomicGauge, histogram::AtomicHistogram};
use ipc::{
collector::{Collector, GlobalInterests},
subscriber::Interests,
};
use crate::*;
pub(crate) static METRIC_INTERESTS: GlobalInterests = GlobalInterests::new();
static EVENT_COUNTERS: AtomicU32Array<TOTAL_EVENT_COUNT> = AtomicU32Array::new();
static CONNECTION_METRICS: [ConnectionMetrics; TOTAL_CONN_TYPES] = init_conn_metrics();
static MESSAGE_INGESTION_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::MessageIngestTime);
static MESSAGE_INDEX_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::MessageIngestIndexTime);
static MESSAGE_DELIVERY_TIME: AtomicHistogram<12> =
AtomicHistogram::<18>::new_long_durations(MetricType::DeliveryTotalTime);
static MESSAGE_INCOMING_SIZE: AtomicHistogram<12> =
AtomicHistogram::<12>::new_message_sizes(MetricType::MessageSize);
static MESSAGE_SUBMISSION_SIZE: AtomicHistogram<12> =
AtomicHistogram::<12>::new_message_sizes(MetricType::MessageAuthenticatedSize);
static MESSAGE_OUT_REPORT_SIZE: AtomicHistogram<12> =
AtomicHistogram::<12>::new_message_sizes(MetricType::OutgoingReportSize);
static STORE_DATA_READ_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::StoreDataReadTime);
static STORE_DATA_WRITE_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::StoreDataWriteTime);
static STORE_BLOB_READ_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::StoreBlobReadTime);
static STORE_BLOB_WRITE_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::StoreBlobWriteTime);
static DNS_LOOKUP_TIME: AtomicHistogram<12> =
AtomicHistogram::<10>::new_short_durations(MetricType::DnsLookupTime);
static SERVER_MEMORY: AtomicGauge = AtomicGauge::new(MetricType::ServerMemory);
static QUEUE_COUNT: AtomicGauge = AtomicGauge::new(MetricType::QueueCount);
static USER_COUNT: AtomicGauge = AtomicGauge::new(MetricType::UserCount);
static DOMAIN_COUNT: AtomicGauge = AtomicGauge::new(MetricType::DomainCount);
const CONN_SMTP_IN: usize = 0;
const CONN_SMTP_OUT: usize = 1;
const CONN_IMAP: usize = 2;
const CONN_POP3: usize = 3;
const CONN_HTTP: usize = 4;
const CONN_SIEVE: usize = 5;
const TOTAL_CONN_TYPES: usize = 6;
pub struct ConnectionMetrics {
pub active_connections: AtomicGauge,
pub elapsed: AtomicHistogram<12>,
}
pub struct EventCounter {
id: EventType,
value: u32,
}
impl Collector {
pub fn record_metric(event: EventType, event_id: usize, keys: &[(Key, Value)]) {
// Increment the event counter
if !event.is_span_end() && !event.is_raw_io() {
EVENT_COUNTERS.add(event_id, 1);
}
// Extract variables
let mut elapsed = 0;
let mut size = 0;
for (key, value) in keys {
match (key, value) {
(Key::Elapsed, Value::Duration(d)) => elapsed = *d,
(Key::Size, Value::UInt(s)) => size = *s,
_ => {}
}
}
match event {
EventType::Smtp(SmtpEvent::ConnectionStart) => {
let conn = &CONNECTION_METRICS[CONN_SMTP_IN];
conn.active_connections.increment();
}
EventType::Smtp(SmtpEvent::ConnectionEnd) => {
let conn = &CONNECTION_METRICS[CONN_SMTP_IN];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::Imap(ImapEvent::ConnectionStart) => {
let conn = &CONNECTION_METRICS[CONN_IMAP];
conn.active_connections.increment();
}
EventType::Imap(ImapEvent::ConnectionEnd) => {
let conn = &CONNECTION_METRICS[CONN_IMAP];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::Pop3(Pop3Event::ConnectionStart) => {
let conn = &CONNECTION_METRICS[CONN_POP3];
conn.active_connections.increment();
}
EventType::Pop3(Pop3Event::ConnectionEnd) => {
let conn = &CONNECTION_METRICS[CONN_POP3];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::Http(HttpEvent::ConnectionStart) => {
let conn = &CONNECTION_METRICS[CONN_HTTP];
conn.active_connections.increment();
}
EventType::Http(HttpEvent::ConnectionEnd) => {
let conn = &CONNECTION_METRICS[CONN_HTTP];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::ManageSieve(ManageSieveEvent::ConnectionStart) => {
let conn = &CONNECTION_METRICS[CONN_SIEVE];
conn.active_connections.increment();
}
EventType::ManageSieve(ManageSieveEvent::ConnectionEnd) => {
let conn = &CONNECTION_METRICS[CONN_SIEVE];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::Delivery(DeliveryEvent::AttemptStart) => {
let conn = &CONNECTION_METRICS[CONN_SMTP_OUT];
conn.active_connections.increment();
}
EventType::Delivery(DeliveryEvent::AttemptEnd) => {
let conn = &CONNECTION_METRICS[CONN_SMTP_OUT];
conn.active_connections.decrement();
conn.elapsed.observe(elapsed);
}
EventType::Delivery(DeliveryEvent::Completed) => {
QUEUE_COUNT.decrement();
MESSAGE_DELIVERY_TIME.observe(elapsed);
}
EventType::Delivery(
DeliveryEvent::MxLookup | DeliveryEvent::IpLookup | DeliveryEvent::NullMx,
)
| EventType::TlsRpt(_)
| EventType::MtaSts(_)
| EventType::Dane(_)
if elapsed > 0 =>
{
DNS_LOOKUP_TIME.observe(elapsed);
}
EventType::MessageIngest(
MessageIngestEvent::Ham
| MessageIngestEvent::Spam
| MessageIngestEvent::ImapAppend
| MessageIngestEvent::JmapAppend,
) => {
MESSAGE_INGESTION_TIME.observe(elapsed);
}
EventType::Queue(QueueEvent::MessageQueued) => {
MESSAGE_INCOMING_SIZE.observe(size);
QUEUE_COUNT.increment();
}
EventType::Queue(QueueEvent::AuthenticatedMessageQueued) => {
MESSAGE_SUBMISSION_SIZE.observe(size);
QUEUE_COUNT.increment();
}
EventType::Queue(QueueEvent::ReportQueued) => {
MESSAGE_OUT_REPORT_SIZE.observe(size);
QUEUE_COUNT.increment();
}
EventType::Queue(QueueEvent::AutogeneratedQueued | QueueEvent::DsnQueued) => {
QUEUE_COUNT.increment();
}
EventType::MessageIngest(MessageIngestEvent::SearchIndex) => {
MESSAGE_INDEX_TIME.observe(elapsed);
}
EventType::Store(StoreEvent::BlobWrite) => {
STORE_BLOB_WRITE_TIME.observe(elapsed);
}
EventType::Store(StoreEvent::BlobRead) => {
STORE_BLOB_READ_TIME.observe(elapsed);
}
EventType::Store(StoreEvent::DataWrite) => {
STORE_DATA_WRITE_TIME.observe(elapsed);
}
EventType::Store(StoreEvent::DataIterate) => {
STORE_DATA_READ_TIME.observe(elapsed);
}
_ => {}
}
}
#[inline(always)]
pub fn is_metric(event: impl Into<usize>) -> bool {
METRIC_INTERESTS.get(event)
}
pub fn set_metrics(interests: Interests) {
METRIC_INTERESTS.update(interests);
}
pub fn collect_counters(_is_enterprise: bool) -> impl Iterator<Item = EventCounter> {
EVENT_COUNTERS
.inner()
.iter()
.enumerate()
.filter_map(|(event_id, value)| {
let value = value.load(Ordering::Relaxed);
if value > 0 {
Some(EventCounter {
id: EventType::from_id(event_id as u16)?,
value,
})
} else {
None
}
})
}
pub fn collect_gauges(is_enterprise: bool) -> impl Iterator<Item = &'static AtomicGauge> {
static E_GAUGES: &[&AtomicGauge] =
&[&SERVER_MEMORY, &QUEUE_COUNT, &USER_COUNT, &DOMAIN_COUNT];
static C_GAUGES: &[&AtomicGauge] = &[&SERVER_MEMORY, &USER_COUNT, &DOMAIN_COUNT];
if is_enterprise { E_GAUGES } else { C_GAUGES }
.iter()
.copied()
.chain(CONNECTION_METRICS.iter().map(|m| &m.active_connections))
}
pub fn collect_histograms(
is_enterprise: bool,
) -> impl Iterator<Item = &'static AtomicHistogram<12>> {
static E_HISTOGRAMS: &[&AtomicHistogram<12>] = &[
&MESSAGE_INGESTION_TIME,
&MESSAGE_INDEX_TIME,
&MESSAGE_DELIVERY_TIME,
&MESSAGE_INCOMING_SIZE,
&MESSAGE_SUBMISSION_SIZE,
&MESSAGE_OUT_REPORT_SIZE,
&STORE_DATA_READ_TIME,
&STORE_DATA_WRITE_TIME,
&STORE_BLOB_READ_TIME,
&STORE_BLOB_WRITE_TIME,
&DNS_LOOKUP_TIME,
];
static C_HISTOGRAMS: &[&AtomicHistogram<12>] = &[
&MESSAGE_DELIVERY_TIME,
&MESSAGE_INCOMING_SIZE,
&MESSAGE_SUBMISSION_SIZE,
];
if is_enterprise {
E_HISTOGRAMS
} else {
C_HISTOGRAMS
}
.iter()
.copied()
.chain(CONNECTION_METRICS.iter().map(|m| &m.elapsed))
.filter(|h| h.is_active())
}
#[inline(always)]
pub fn read_metric_counter(metric_id: usize) -> u32 {
EVENT_COUNTERS.get(metric_id)
}
pub fn read_metric(metric_type: MetricType) -> f64 {
match metric_type {
MetricType::ServerMemory => SERVER_MEMORY.get() as f64,
MetricType::MessageIngestTime => MESSAGE_INGESTION_TIME.average(),
MetricType::MessageIngestIndexTime => MESSAGE_INDEX_TIME.average(),
MetricType::MessageSize => MESSAGE_INCOMING_SIZE.average(),
MetricType::MessageAuthenticatedSize => MESSAGE_SUBMISSION_SIZE.average(),
MetricType::DeliveryTotalTime => MESSAGE_DELIVERY_TIME.average(),
MetricType::DeliveryAttemptTime => CONNECTION_METRICS[CONN_SMTP_OUT].elapsed.average(),
MetricType::DeliveryActiveConnections => {
CONNECTION_METRICS[CONN_SMTP_OUT].active_connections.get() as f64
}
MetricType::QueueCount => QUEUE_COUNT.get() as f64,
MetricType::OutgoingReportSize => MESSAGE_OUT_REPORT_SIZE.average(),
MetricType::StoreDataReadTime => STORE_DATA_READ_TIME.average(),
MetricType::StoreDataWriteTime => STORE_DATA_WRITE_TIME.average(),
MetricType::StoreBlobReadTime => STORE_BLOB_READ_TIME.average(),
MetricType::StoreBlobWriteTime => STORE_BLOB_WRITE_TIME.average(),
MetricType::DnsLookupTime => DNS_LOOKUP_TIME.average(),
MetricType::HttpActiveConnections => {
CONNECTION_METRICS[CONN_HTTP].active_connections.get() as f64
}
MetricType::HttpRequestTime => CONNECTION_METRICS[CONN_HTTP].elapsed.average(),
MetricType::ImapActiveConnections => {
CONNECTION_METRICS[CONN_IMAP].active_connections.get() as f64
}
MetricType::ImapRequestTime => CONNECTION_METRICS[CONN_IMAP].elapsed.average(),
MetricType::Pop3ActiveConnections => {
CONNECTION_METRICS[CONN_POP3].active_connections.get() as f64
}
MetricType::Pop3RequestTime => CONNECTION_METRICS[CONN_POP3].elapsed.average(),
MetricType::SmtpActiveConnections => {
CONNECTION_METRICS[CONN_SMTP_IN].active_connections.get() as f64
}
MetricType::SmtpRequestTime => CONNECTION_METRICS[CONN_SMTP_IN].elapsed.average(),
MetricType::SieveActiveConnections => {
CONNECTION_METRICS[CONN_SIEVE].active_connections.get() as f64
}
MetricType::SieveRequestTime => CONNECTION_METRICS[CONN_SIEVE].elapsed.average(),
MetricType::UserCount => USER_COUNT.get() as f64,
MetricType::DomainCount => DOMAIN_COUNT.get() as f64,
_ => EVENT_COUNTERS.get(metric_type.event_id()) as f64,
}
}
pub fn update_gauge(metric_type: MetricType, value: u64) {
match metric_type {
MetricType::ServerMemory => SERVER_MEMORY.set(value),
MetricType::QueueCount => QUEUE_COUNT.set(value),
MetricType::UserCount => USER_COUNT.set(value),
MetricType::DomainCount => DOMAIN_COUNT.set(value),
_ => {}
}
}
pub fn update_event_counter(event_type: EventType, value: u32) {
EVENT_COUNTERS.add(event_type.into(), value);
}
pub fn update_histogram(metric_type: MetricType, value: u64) {
match metric_type {
MetricType::MessageIngestTime => MESSAGE_INGESTION_TIME.observe(value),
MetricType::MessageIngestIndexTime => MESSAGE_INDEX_TIME.observe(value),
MetricType::DeliveryTotalTime => MESSAGE_DELIVERY_TIME.observe(value),
MetricType::DeliveryAttemptTime => {
CONNECTION_METRICS[CONN_SMTP_OUT].elapsed.observe(value)
}
MetricType::DnsLookupTime => DNS_LOOKUP_TIME.observe(value),
MetricType::StoreDataReadTime => STORE_DATA_READ_TIME.observe(value),
MetricType::StoreDataWriteTime => STORE_DATA_WRITE_TIME.observe(value),
MetricType::StoreBlobReadTime => STORE_BLOB_READ_TIME.observe(value),
MetricType::StoreBlobWriteTime => STORE_BLOB_WRITE_TIME.observe(value),
_ => {}
}
}
}
impl EventCounter {
pub fn id(&self) -> EventType {
self.id
}
pub fn value(&self) -> u64 {
self.value as u64
}
}
impl ConnectionMetrics {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
active_connections: AtomicGauge::new(MetricType::StoreBlobReadTime),
elapsed: AtomicHistogram::<18>::new_medium_durations(MetricType::StoreBlobReadTime),
}
}
}
#[allow(clippy::declare_interior_mutable_const)]
const fn init_conn_metrics() -> [ConnectionMetrics; TOTAL_CONN_TYPES] {
const INIT: ConnectionMetrics = ConnectionMetrics::new();
let mut array = [INIT; TOTAL_CONN_TYPES];
let mut i = 0;
while i < TOTAL_CONN_TYPES {
let metric = match i {
CONN_HTTP => &[
MetricType::HttpRequestTime,
MetricType::HttpActiveConnections,
],
CONN_IMAP => &[
MetricType::ImapRequestTime,
MetricType::ImapActiveConnections,
],
CONN_POP3 => &[
MetricType::Pop3RequestTime,
MetricType::Pop3ActiveConnections,
],
CONN_SMTP_IN => &[
MetricType::SmtpRequestTime,
MetricType::SmtpActiveConnections,
],
CONN_SMTP_OUT => &[
MetricType::DeliveryAttemptTime,
MetricType::DeliveryActiveConnections,
],
CONN_SIEVE => &[
MetricType::SieveRequestTime,
MetricType::SieveActiveConnections,
],
_ => &[MetricType::StoreBlobReadTime, MetricType::StoreBlobReadTime],
};
array[i] = ConnectionMetrics {
elapsed: AtomicHistogram::<18>::new_medium_durations(metric[0]),
active_connections: AtomicGauge::new(metric[1]),
};
i += 1;
}
array
}
+14
View File
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod bitset;
pub mod channel;
pub mod collector;
pub mod metrics;
pub mod subscriber;
pub(crate) const USIZE_BITS: usize = std::mem::size_of::<usize>() * 8;
pub(crate) const USIZE_BITS_MASK: usize = USIZE_BITS - 1;
+126
View File
@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::sync::Arc;
use tokio::sync::mpsc::{self, error::TrySendError};
use crate::{Event, EventDetails, EventType, Level, TOTAL_EVENT_COUNT};
use super::{
USIZE_BITS,
bitset::Bitset,
channel::ChannelError,
collector::{COLLECTOR_UPDATES, Collector, Update},
};
const MAX_BATCH_SIZE: usize = 32768;
pub type Interests = Box<Bitset<{ TOTAL_EVENT_COUNT.div_ceil(USIZE_BITS) }>>;
pub type EventBatch = Vec<Arc<Event<EventDetails>>>;
#[derive(Debug)]
pub(crate) struct Subscriber {
pub id: String,
pub interests: Interests,
pub tx: mpsc::Sender<EventBatch>,
pub lossy: bool,
pub batch: EventBatch,
}
pub struct SubscriberBuilder {
pub id: String,
pub interests: Interests,
pub lossy: bool,
}
impl Subscriber {
#[inline(always)]
pub fn push_event(&mut self, event_id: usize, trace: Arc<Event<EventDetails>>) {
if self.interests.get(event_id) {
self.batch.push(trace);
}
}
pub fn send_batch(&mut self) -> Result<(), ChannelError> {
if !self.batch.is_empty() {
match self
.tx
.try_send(std::mem::replace(&mut self.batch, Vec::with_capacity(128)))
{
Ok(_) => Ok(()),
Err(TrySendError::Full(mut events)) => {
if self.lossy && events.len() > MAX_BATCH_SIZE {
events.retain(|e| e.inner.level == Level::Error);
if events.len() > MAX_BATCH_SIZE {
events.truncate(MAX_BATCH_SIZE);
}
}
self.batch = events;
Ok(())
}
Err(TrySendError::Closed(_)) => Err(ChannelError),
}
} else {
Ok(())
}
}
}
impl SubscriberBuilder {
pub fn new(id: String) -> Self {
Self {
id,
interests: Default::default(),
lossy: true,
}
}
pub fn with_default_interests(mut self, level: Level) -> Self {
for event in EventType::variants() {
if event.level() >= level {
self.interests.set(*event);
}
}
self
}
pub fn with_interests(mut self, interests: Interests) -> Self {
self.interests = interests;
self
}
pub fn set_interests(mut self, interest: impl IntoIterator<Item = impl Into<usize>>) -> Self {
for level in interest {
self.interests.set(level);
}
self
}
pub fn with_lossy(mut self, lossy: bool) -> Self {
self.lossy = lossy;
self
}
pub fn register(self) -> (mpsc::Sender<EventBatch>, mpsc::Receiver<EventBatch>) {
let (tx, rx) = mpsc::channel(8192);
COLLECTOR_UPDATES.lock().push(Update::RegisterSubscriber {
subscriber: Subscriber {
id: self.id,
interests: self.interests,
tx: tx.clone(),
lossy: self.lossy,
batch: Vec::new(),
},
});
// Notify collector
Collector::reload();
(tx, rx)
}
}
+91
View File
@@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#![warn(clippy::large_futures)]
pub mod atomics;
pub mod event;
pub mod ipc;
pub mod macros;
pub mod serializers;
pub use crate::event::enums::*;
pub use crate::ipc::collector::Collector;
use compact_str::CompactString;
pub use event_macro::event;
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::Arc,
};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct Error(Box<Event<EventType>>);
#[derive(Debug, Clone)]
pub struct Event<T> {
pub inner: T,
pub keys: Vec<(Key, Value)>,
}
#[derive(Debug, Clone)]
pub struct EventDetails {
pub typ: EventType,
pub timestamp: u64,
pub level: Level,
pub span: Option<Arc<Event<EventDetails>>>,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[repr(usize)]
pub enum Level {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Disable = 5,
}
#[derive(Debug, Default, Clone)]
pub enum Value {
String(CompactString),
UInt(u64),
Int(i64),
Float(f64),
Timestamp(u64),
Duration(u64),
Bytes(Vec<u8>),
Bool(bool),
Ipv4(Ipv4Addr),
Ipv6(Ipv6Addr),
Event(Error),
Array(Vec<Value>),
#[default]
None,
}
pub trait AddContext<T> {
fn caused_by(self, location: &'static str) -> Result<T>;
fn add_context<F>(self, f: F) -> Result<T>
where
F: FnOnce(Error) -> Error;
}
#[allow(clippy::derivable_impls)]
impl Default for MetricType {
fn default() -> Self {
MetricType::UserCount
}
}
impl Default for EventType {
fn default() -> Self {
EventType::Store(StoreEvent::UnexpectedError)
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
#[macro_export]
macro_rules! location {
() => {{ concat!(file!(), ":", line!()) }};
}
#[macro_export]
macro_rules! bail {
($err:expr $(,)?) => {
return Err($err);
};
}
#[macro_export]
macro_rules! error {
($err:expr $(,)?) => {
let err = $err;
let event_id = err.as_ref().to_id() as usize;
if $crate::Collector::is_metric(event_id) {
$crate::Collector::record_metric(*err.as_ref(), event_id, err.keys());
}
if $crate::Collector::has_interest(event_id) {
err.send();
}
};
}
+271
View File
@@ -0,0 +1,271 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{Error, Event, EventDetails, EventType, Key, MetricType, Value};
use ahash::AHashSet;
use base64::{Engine, engine::general_purpose::STANDARD};
use mail_parser::DateTime;
use serde::{
Serialize, Serializer,
ser::{SerializeMap, SerializeSeq},
};
use std::sync::atomic::{AtomicU64, Ordering};
static EVENT_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
struct Keys<'x> {
keys: &'x [(Key, Value)],
span_keys: &'x [(Key, Value)],
}
pub struct JsonEventSerializer<T> {
inner: T,
with_id: bool,
with_spans: bool,
with_description: bool,
}
impl<T> JsonEventSerializer<T> {
pub fn new(inner: T) -> Self {
Self {
inner,
with_id: false,
with_spans: false,
with_description: false,
}
}
pub fn with_id(mut self) -> Self {
self.with_id = true;
self
}
pub fn with_spans(mut self) -> Self {
self.with_spans = true;
self
}
pub fn with_description(mut self) -> Self {
self.with_description = true;
self
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: AsRef<Event<EventDetails>>> Serialize for JsonEventSerializer<Vec<T>> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.inner.len()))?;
for event in &self.inner {
seq.serialize_element(&JsonEventSerializer {
inner: event,
with_id: self.with_id,
with_spans: self.with_spans,
with_description: self.with_description,
})?;
}
seq.end()
}
}
impl<T: AsRef<Event<EventDetails>>> Serialize for JsonEventSerializer<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let event = self.inner.as_ref();
let mut map = serializer.serialize_map(None)?;
if self.with_id {
map.serialize_entry(
"id",
&format!(
"{}{}{}",
event.inner.timestamp,
EVENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed),
event.inner.typ.to_id()
),
)?;
}
if self.with_description {
map.serialize_entry("text", event.inner.typ.description())?;
}
map.serialize_entry(
"createdAt",
&DateTime::from_timestamp(event.inner.timestamp as i64).to_rfc3339(),
)?;
map.serialize_entry("type", event.inner.typ.as_str())?;
map.serialize_entry(
"data",
&JsonEventSerializer {
inner: Keys {
keys: event.keys.as_slice(),
span_keys: event
.inner
.span
.as_ref()
.map(|s| &s.keys[..])
.unwrap_or(&[]),
},
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
},
)?;
map.end()
}
}
impl Serialize for JsonEventSerializer<Keys<'_>> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let keys_len = self.inner.keys.len() + self.inner.span_keys.len();
let mut seen_keys = AHashSet::with_capacity(keys_len);
let mut keys = serializer.serialize_map(Some(keys_len))?;
for (key, value) in self.inner.keys.iter().chain(self.inner.span_keys.iter()) {
if !matches!(value, Value::None)
&& (self.with_spans || !matches!(key, Key::SpanId))
&& seen_keys.insert(*key)
{
keys.serialize_entry(
key.as_str(),
&JsonEventSerializer {
inner: value,
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
},
)?;
}
}
keys.end()
}
}
impl Serialize for JsonEventSerializer<&Error> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", self.inner.0.inner.as_str())?;
if self.with_description {
map.serialize_entry("text", self.inner.0.inner.description())?;
}
map.serialize_entry(
"data",
&JsonEventSerializer {
inner: Keys {
keys: self.inner.0.keys.as_slice(),
span_keys: &[],
},
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
},
)?;
map.end()
}
}
impl Serialize for JsonEventSerializer<&Value> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.inner {
Value::String(value) => value.serialize(serializer),
Value::UInt(value) => value.serialize(serializer),
Value::Int(value) => value.serialize(serializer),
Value::Float(value) => value.serialize(serializer),
Value::Timestamp(value) => DateTime::from_timestamp(*value as i64)
.to_rfc3339()
.serialize(serializer),
Value::Duration(value) => value.serialize(serializer),
Value::Bytes(value) => STANDARD.encode(value).serialize(serializer),
Value::Bool(value) => value.serialize(serializer),
Value::Ipv4(value) => value.serialize(serializer),
Value::Ipv6(value) => value.serialize(serializer),
Value::Event(value) => JsonEventSerializer {
inner: value,
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
}
.serialize(serializer),
Value::Array(value) => JsonEventSerializer {
inner: value,
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
}
.serialize(serializer),
Value::None => unreachable!(),
}
}
}
impl Serialize for JsonEventSerializer<&Vec<Value>> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.inner.len()))?;
for value in self.inner {
seq.serialize_element(&JsonEventSerializer {
inner: value,
with_spans: self.with_spans,
with_description: self.with_description,
with_id: self.with_id,
})?;
}
seq.end()
}
}
impl serde::Serialize for EventType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for EventType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Self::parse(s).ok_or_else(|| serde::de::Error::unknown_variant(s, &[]))
}
}
impl serde::Serialize for MetricType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for MetricType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Self::parse(s).ok_or_else(|| serde::de::Error::unknown_variant(s, &[]))
}
}
+9
View File
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod json;
pub mod text;
+374
View File
@@ -0,0 +1,374 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Display;
use mail_parser::DateTime;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::{Error, Event, EventDetails, Key, Level, Value};
use base64::{Engine, engine::general_purpose::STANDARD};
pub struct FmtWriter<T: AsyncWrite + Unpin> {
writer: T,
ansi: bool,
multiline: bool,
}
#[allow(dead_code)]
enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl<T: AsyncWrite + Unpin> FmtWriter<T> {
pub fn new(writer: T) -> Self {
Self {
writer,
ansi: false,
multiline: false,
}
}
pub fn with_ansi(self, ansi: bool) -> Self {
Self { ansi, ..self }
}
pub fn with_multiline(self, multiline: bool) -> Self {
Self { multiline, ..self }
}
pub async fn write(&mut self, event: &Event<EventDetails>) -> std::io::Result<()> {
// Write timestamp
if self.ansi {
self.writer
.write_all(Color::White.as_code().as_bytes())
.await?;
}
self.writer
.write_all(
DateTime::from_timestamp(event.inner.timestamp as i64)
.to_rfc3339()
.as_bytes(),
)
.await?;
if self.ansi {
self.writer.write_all(Color::reset().as_bytes()).await?;
}
self.writer.write_all(" ".as_bytes()).await?;
// Write level
if self.ansi {
self.writer
.write_all(
match event.inner.level {
Level::Error => Color::Red,
Level::Warn => Color::Yellow,
Level::Info => Color::Green,
Level::Debug => Color::Blue,
Level::Trace => Color::Magenta,
Level::Disable => return Ok(()),
}
.as_code_bold()
.as_bytes(),
)
.await?;
}
self.writer
.write_all(event.inner.level.as_str().as_bytes())
.await?;
if self.ansi {
self.writer.write_all(Color::reset().as_bytes()).await?;
}
self.writer.write_all(" ".as_bytes()).await?;
// Write message
if self.ansi {
self.writer
.write_all(Color::White.as_code_bold().as_bytes())
.await?;
}
self.writer
.write_all(event.inner.typ.description().as_bytes())
.await?;
if self.ansi {
self.writer.write_all(Color::reset().as_bytes()).await?;
}
self.writer.write_all(" (".as_bytes()).await?;
self.writer
.write_all(event.inner.typ.as_str().as_bytes())
.await?;
self.writer
.write_all(if self.multiline { ")\n" } else { ") " }.as_bytes())
.await?;
// Write keys
if let Some(parent_event) = &event.inner.span {
self.write_keys(&parent_event.keys, &event.keys, 1).await?;
} else {
self.write_keys(&[], &event.keys, 1).await?;
}
if !self.multiline {
self.writer.write_all("\n".as_bytes()).await?;
}
Ok(())
}
async fn write_keys(
&mut self,
span_keys: &[(Key, Value)],
keys: &[(Key, Value)],
indent: usize,
) -> std::io::Result<()> {
Box::pin(async move {
let mut is_first = true;
for (key, value) in span_keys.iter().chain(keys.iter()) {
if matches!(key, Key::SpanId) {
continue;
} else if is_first {
is_first = false;
} else if !self.multiline {
self.writer.write_all(", ".as_bytes()).await?;
}
// Write key
if self.multiline {
for _ in 0..indent {
self.writer.write_all("\t".as_bytes()).await?;
}
}
if self.ansi {
self.writer
.write_all(Color::Cyan.as_code().as_bytes())
.await?;
}
self.writer.write_all(key.as_str().as_bytes()).await?;
if self.ansi {
self.writer.write_all(Color::reset().as_bytes()).await?;
}
// Write value
self.writer.write_all(" = ".as_bytes()).await?;
self.write_value(value, indent).await?;
if self.multiline && !matches!(value, Value::Event(_)) {
self.writer.write_all("\n".as_bytes()).await?;
}
}
Ok(())
})
.await
}
async fn write_value(&mut self, value: &Value, indent: usize) -> std::io::Result<()> {
Box::pin(async move {
match value {
Value::String(v) => {
self.writer.write_all("\"".as_bytes()).await?;
for ch in v.as_bytes() {
match ch {
b'\r' => {
self.writer.write_all("\\r".as_bytes()).await?;
}
b'\n' => {
self.writer.write_all("\\n".as_bytes()).await?;
}
b'\t' => {
self.writer.write_all("\\t".as_bytes()).await?;
}
b'\\' => {
self.writer.write_all("\\\\".as_bytes()).await?;
}
_ => {
self.writer.write_all(&[*ch]).await?;
}
}
}
self.writer.write_all("\"".as_bytes()).await?;
}
Value::UInt(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
}
Value::Int(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
}
Value::Float(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
}
Value::Timestamp(v) => {
self.writer
.write_all(DateTime::from_timestamp(*v as i64).to_rfc3339().as_bytes())
.await?;
}
Value::Duration(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
self.writer.write_all("ms".as_bytes()).await?;
}
Value::Bytes(bytes) => {
self.writer.write_all("base64:".as_bytes()).await?;
self.writer
.write_all(STANDARD.encode(bytes).as_bytes())
.await?;
}
Value::Bool(true) => {
self.writer.write_all("true".as_bytes()).await?;
}
Value::Bool(false) => {
self.writer.write_all("false".as_bytes()).await?;
}
Value::Ipv4(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
}
Value::Ipv6(v) => {
self.writer.write_all(v.to_string().as_bytes()).await?;
}
Value::Event(e) => {
self.writer
.write_all(e.0.inner.description().as_bytes())
.await?;
self.writer.write_all(" (".as_bytes()).await?;
self.writer.write_all(e.0.inner.as_str().as_bytes()).await?;
self.writer.write_all(")".as_bytes()).await?;
if !e.0.keys.is_empty() {
self.writer
.write_all(if self.multiline { "\n" } else { " { " }.as_bytes())
.await?;
self.write_keys(&e.0.keys, &[], indent + 1).await?;
if !self.multiline {
self.writer.write_all(" }".as_bytes()).await?;
}
} else if self.multiline {
self.writer.write_all("\n".as_bytes()).await?;
}
}
Value::Array(arr) => {
self.writer.write_all("[".as_bytes()).await?;
for (pos, value) in arr.iter().enumerate() {
if pos > 0 {
self.writer.write_all(", ".as_bytes()).await?;
}
self.write_value(value, indent).await?;
}
self.writer.write_all("]".as_bytes()).await?;
}
Value::None => {
self.writer.write_all("(null)".as_bytes()).await?;
}
}
Ok(())
})
.await
}
pub async fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush().await
}
pub fn update_writer(&mut self, writer: T) {
self.writer = writer;
}
}
impl Color {
pub fn as_code(&self) -> &'static str {
match self {
Color::Black => "\x1b[30m",
Color::Red => "\x1b[31m",
Color::Green => "\x1b[32m",
Color::Yellow => "\x1b[33m",
Color::Blue => "\x1b[34m",
Color::Magenta => "\x1b[35m",
Color::Cyan => "\x1b[36m",
Color::White => "\x1b[37m",
}
}
pub fn as_code_bold(&self) -> &'static str {
match self {
Color::Black => "\x1b[30;1m",
Color::Red => "\x1b[31;1m",
Color::Green => "\x1b[32;1m",
Color::Yellow => "\x1b[33;1m",
Color::Blue => "\x1b[34;1m",
Color::Magenta => "\x1b[35;1m",
Color::Cyan => "\x1b[36;1m",
Color::White => "\x1b[37;1m",
}
}
pub fn reset() -> &'static str {
"\x1b[0m"
}
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::String(value) => value.fmt(f),
Value::UInt(value) => value.fmt(f),
Value::Int(value) => value.fmt(f),
Value::Float(value) => value.fmt(f),
Value::Timestamp(value) => value.fmt(f),
Value::Duration(value) => value.fmt(f),
Value::Bytes(value) => STANDARD.encode(value).fmt(f),
Value::Bool(value) => value.fmt(f),
Value::Ipv4(value) => value.fmt(f),
Value::Ipv6(value) => value.fmt(f),
Value::Event(value) => {
"{".fmt(f)?;
value.fmt(f)?;
"}".fmt(f)
}
Value::Array(value) => {
f.write_str("[")?;
for (i, value) in value.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
value.fmt(f)?;
}
f.write_str("]")
}
Value::None => "(null)".fmt(f),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.inner.description().fmt(f)?;
" (".fmt(f)?;
self.0.inner.as_str().fmt(f)?;
")".fmt(f)?;
if !self.0.keys.is_empty() {
f.write_str(": ")?;
for (i, (key, value)) in self.0.keys.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
key.as_str().fmt(f)?;
f.write_str(" = ")?;
value.fmt(f)?;
}
}
Ok(())
}
}