Lock-free fast tracing (closes #180)
This commit is contained in:
@@ -9,6 +9,11 @@ base64 = "0.22.1"
|
||||
serde_json = "1.0.120"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]}
|
||||
bincode = "1.3.3"
|
||||
rtrb = "0.3.1"
|
||||
parking_lot = "0.12.3"
|
||||
tokio = { version = "1.23", features = ["net", "macros"] }
|
||||
ahash = "0.8.11"
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
[features]
|
||||
test_mode = []
|
||||
|
||||
99
crates/trc/src/channel.rs
Normal file
99
crates/trc/src/channel.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use rtrb::{Consumer, Producer, PushError, RingBuffer};
|
||||
|
||||
use crate::{
|
||||
collector::{spawn_collector, CollectorThread},
|
||||
Event,
|
||||
};
|
||||
|
||||
pub(crate) static EVENT_RXS: Mutex<Vec<Receiver>> = Mutex::new(Vec::new());
|
||||
pub(crate) static EVENT_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
pub(crate) const CHANNEL_SIZE: usize = 10240;
|
||||
|
||||
thread_local! {
|
||||
static EVENT_TX: UnsafeCell<Sender> = {
|
||||
let (tx, rx) = RingBuffer::new(CHANNEL_SIZE);
|
||||
EVENT_RXS.lock().push(Receiver { rx });
|
||||
UnsafeCell::new(Sender {
|
||||
tx,
|
||||
collector: spawn_collector().clone(),
|
||||
overflow: Vec::with_capacity(0),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Sender {
|
||||
tx: Producer<Arc<Event>>,
|
||||
collector: Arc<CollectorThread>,
|
||||
overflow: Vec<Arc<Event>>,
|
||||
}
|
||||
|
||||
pub struct Receiver {
|
||||
rx: Consumer<Arc<Event>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelError;
|
||||
|
||||
impl Sender {
|
||||
pub fn send(&mut self, event: Arc<Event>) -> 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<Arc<Event>>, ChannelError> {
|
||||
match self.rx.pop() {
|
||||
Ok(event) => Ok(Some(event)),
|
||||
Err(_) => {
|
||||
if !self.rx.is_abandoned() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(ChannelError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
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(Arc::new(self)).is_ok() {
|
||||
EVENT_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
tx.collector.thread().unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
128
crates/trc/src/collector.rs
Normal file
128
crates/trc/src/collector.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, OnceLock,
|
||||
},
|
||||
thread::{park, Builder, JoinHandle},
|
||||
};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::{
|
||||
channel::{EVENT_COUNT, EVENT_RXS},
|
||||
subscriber::{Subscriber, SUBSCRIBER_UPDATE},
|
||||
Event, EventType, Level,
|
||||
};
|
||||
|
||||
pub(crate) static TRACING_LEVEL: AtomicUsize = AtomicUsize::new(Level::Info as usize);
|
||||
|
||||
pub(crate) type CollectorThread = JoinHandle<()>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Collector {
|
||||
subscribers: Vec<Subscriber>,
|
||||
}
|
||||
|
||||
impl Collector {
|
||||
fn collect(&mut self) -> bool {
|
||||
if EVENT_COUNT.swap(0, Ordering::Relaxed) == 0 {
|
||||
park();
|
||||
}
|
||||
|
||||
// Collect all events
|
||||
let mut do_continue = true;
|
||||
EVENT_RXS.lock().retain_mut(|rx| {
|
||||
loop {
|
||||
match rx.try_recv() {
|
||||
Ok(Some(event)) => {
|
||||
if !event.keys.is_empty() {
|
||||
// Process events
|
||||
for subscriber in self.subscribers.iter_mut() {
|
||||
subscriber.push_event(event.clone());
|
||||
}
|
||||
} else {
|
||||
// Register subscriber
|
||||
let subscribers = { std::mem::take(&mut (*SUBSCRIBER_UPDATE.lock())) };
|
||||
if !subscribers.is_empty() {
|
||||
self.subscribers.extend(subscribers);
|
||||
} else if event.level == Level::Disable {
|
||||
do_continue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
return true;
|
||||
}
|
||||
Err(_) => {
|
||||
return false; // Channel is closed, remove.
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !self.subscribers.is_empty() {
|
||||
self.subscribers
|
||||
.retain_mut(|subscriber| subscriber.send_batch().is_ok());
|
||||
}
|
||||
|
||||
do_continue
|
||||
}
|
||||
|
||||
pub fn set_level(level: Level) {
|
||||
TRACING_LEVEL.store(level as usize, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn update_custom_levels(levels: AHashMap<EventType, Level>) {
|
||||
custom_levels().store(Arc::new(levels));
|
||||
}
|
||||
|
||||
pub fn shutdown() {
|
||||
Event::new(EventType::Error(crate::Cause::Thread), Level::Disable, 0).send()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_collector() -> &'static Arc<CollectorThread> {
|
||||
static COLLECTOR: OnceLock<Arc<CollectorThread>> = OnceLock::new();
|
||||
COLLECTOR.get_or_init(|| {
|
||||
Arc::new(
|
||||
Builder::new()
|
||||
.name("stalwart-collector".to_string())
|
||||
.spawn(move || {
|
||||
let mut collector = Collector::default();
|
||||
|
||||
while collector.collect() {}
|
||||
})
|
||||
.expect("Failed to start event collector"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn custom_levels() -> &'static ArcSwap<AHashMap<EventType, Level>> {
|
||||
static CUSTOM_LEVELS: OnceLock<ArcSwap<AHashMap<EventType, Level>>> = OnceLock::new();
|
||||
CUSTOM_LEVELS.get_or_init(|| ArcSwap::from_pointee(Default::default()))
|
||||
}
|
||||
|
||||
impl EventType {
|
||||
#[inline(always)]
|
||||
pub fn effective_level(&self) -> Level {
|
||||
custom_levels()
|
||||
.load()
|
||||
.get(self)
|
||||
.copied()
|
||||
.unwrap_or_else(|| self.level())
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
#[inline(always)]
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
*self as usize >= TRACING_LEVEL.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl<T, const N: usize> AsRef<T> for Context<T, N> {
|
||||
fn as_ref(&self) -> &T {
|
||||
impl AsRef<Cause> for Error {
|
||||
fn as_ref(&self) -> &Cause {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,36 +4,44 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::{borrow::Cow, cmp::Ordering, fmt::Display};
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl<T, const N: usize> Context<T, N>
|
||||
where
|
||||
[(Key, Value); N]: Default,
|
||||
T: Eq,
|
||||
{
|
||||
pub fn new(inner: T) -> Self {
|
||||
impl Event {
|
||||
pub fn new(inner: EventType, level: Level, capacity: usize) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
keys: Default::default(),
|
||||
keys_size: 0,
|
||||
level,
|
||||
keys: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ctx(mut self, key: Key, value: impl Into<Value>) -> Self {
|
||||
if self.keys_size < N {
|
||||
self.keys[self.keys_size] = (key, value.into());
|
||||
self.keys_size += 1;
|
||||
} else {
|
||||
#[cfg(debug_assertions)]
|
||||
panic!(
|
||||
"Context is full while inserting {:?}: {:?}",
|
||||
key,
|
||||
value.into()
|
||||
);
|
||||
self.keys.push((key, value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ctx_opt(self, key: Key, value: Option<impl Into<Value>>) -> Self {
|
||||
match value {
|
||||
Some(value) => self.ctx(key, value),
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(inner: Cause) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
keys: Vec::with_capacity(5),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ctx(mut self, key: Key, value: impl Into<Value>) -> Self {
|
||||
self.keys.push((key, value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -45,20 +53,14 @@ where
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn matches(&self, inner: T) -> bool {
|
||||
pub fn matches(&self, inner: Cause) -> bool {
|
||||
self.inner == inner
|
||||
}
|
||||
|
||||
pub fn value(&self, key: Key) -> Option<&Value> {
|
||||
self.keys.iter().take(self.keys_size).find_map(
|
||||
|(k, v)| {
|
||||
if *k == key {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
self.keys
|
||||
.iter()
|
||||
.find_map(|(k, v)| if *k == key { Some(v) } else { None })
|
||||
}
|
||||
|
||||
pub fn value_as_str(&self, key: Key) -> Option<&str> {
|
||||
@@ -66,16 +68,13 @@ where
|
||||
}
|
||||
|
||||
pub fn take_value(&mut self, key: Key) -> Option<Value> {
|
||||
self.keys
|
||||
.iter_mut()
|
||||
.take(self.keys_size)
|
||||
.find_map(|(k, v)| {
|
||||
if *k == key {
|
||||
Some(std::mem::take(v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.keys.iter_mut().find_map(|(k, v)| {
|
||||
if *k == key {
|
||||
Some(std::mem::take(v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -166,7 +165,6 @@ impl Cause {
|
||||
Self::Pop3 => "POP3 error",
|
||||
Self::Smtp => "SMTP error",
|
||||
Self::Thread => "Thread error",
|
||||
Self::Fetch => "Fetch error",
|
||||
Self::Acme => "ACME error",
|
||||
Self::Dns => "DNS error",
|
||||
Self::Ingest => "Message Ingest error",
|
||||
@@ -174,7 +172,6 @@ impl Cause {
|
||||
Self::Limit(cause) => cause.message(),
|
||||
Self::Manage(cause) => cause.message(),
|
||||
Self::Auth(cause) => cause.message(),
|
||||
Self::Purge => "Purge error",
|
||||
Self::Configuration => "Configuration error",
|
||||
Self::Resource(cause) => cause.message(),
|
||||
}
|
||||
@@ -489,10 +486,10 @@ impl<T> AddContext<T> for Result<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Debug, const N: usize> Display for Context<T, N> {
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.inner)?;
|
||||
for (key, value) in self.keys.iter().take(self.keys_size) {
|
||||
for (key, value) in self.keys.iter() {
|
||||
write!(f, "\n {:?} = {:?}", key, value)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -501,6 +498,40 @@ impl<T: std::fmt::Debug, const N: usize> Display for Context<T, N> {
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
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 PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
@@ -525,14 +556,11 @@ impl PartialEq for Value {
|
||||
|
||||
impl Eq for Value {}
|
||||
|
||||
impl<T, const N: usize> PartialEq for Context<T, N>
|
||||
where
|
||||
T: Eq,
|
||||
{
|
||||
impl PartialEq for Error {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.inner == other.inner && self.keys_size == other.keys_size {
|
||||
for kv in self.keys.iter().take(self.keys_size) {
|
||||
if !other.keys.iter().take(other.keys_size).any(|okv| kv == okv) {
|
||||
if self.inner == other.inner && self.keys.len() == other.keys.len() {
|
||||
for kv in self.keys.iter() {
|
||||
if !other.keys.iter().any(|okv| kv == okv) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -544,4 +572,57 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Eq for Context<T, N> where T: Eq {}
|
||||
impl Eq for Error {}
|
||||
|
||||
impl EventType {
|
||||
pub fn level(&self) -> Level {
|
||||
match self {
|
||||
EventType::Error(error) => match error {
|
||||
Cause::Store(_) => Level::Error,
|
||||
Cause::Jmap(_) => Level::Debug,
|
||||
Cause::Imap => Level::Debug,
|
||||
Cause::ManageSieve => Level::Debug,
|
||||
Cause::Pop3 => Level::Debug,
|
||||
Cause::Smtp => Level::Debug,
|
||||
Cause::Thread => Level::Error,
|
||||
Cause::Acme => Level::Error,
|
||||
Cause::Dns => Level::Error,
|
||||
Cause::Ingest => Level::Error,
|
||||
Cause::Network => Level::Debug,
|
||||
Cause::Limit(cause) => match cause {
|
||||
LimitCause::SizeRequest => Level::Debug,
|
||||
LimitCause::SizeUpload => Level::Debug,
|
||||
LimitCause::CallsIn => Level::Debug,
|
||||
LimitCause::ConcurrentRequest => Level::Debug,
|
||||
LimitCause::ConcurrentUpload => Level::Debug,
|
||||
LimitCause::Quota => Level::Debug,
|
||||
LimitCause::BlobQuota => Level::Debug,
|
||||
LimitCause::TooManyRequests => Level::Warn,
|
||||
},
|
||||
Cause::Manage(_) => Level::Debug,
|
||||
Cause::Auth(cause) => match cause {
|
||||
AuthCause::Failed => Level::Debug,
|
||||
AuthCause::MissingTotp => Level::Trace,
|
||||
AuthCause::TooManyAttempts => Level::Warn,
|
||||
AuthCause::Banned => Level::Warn,
|
||||
AuthCause::Error => Level::Error,
|
||||
},
|
||||
Cause::Configuration => Level::Error,
|
||||
Cause::Resource(cause) => match cause {
|
||||
ResourceCause::NotFound => Level::Debug,
|
||||
ResourceCause::BadParameters => Level::Error,
|
||||
ResourceCause::Error => Level::Error,
|
||||
},
|
||||
},
|
||||
EventType::NewConnection => Level::Info,
|
||||
EventType::SqlQuery => Level::Trace,
|
||||
EventType::LdapQuery => Level::Trace,
|
||||
EventType::Purge(event) => match event {
|
||||
PurgeEvent::Started => Level::Debug,
|
||||
PurgeEvent::Finished => Level::Debug,
|
||||
PurgeEvent::Running => Level::Info,
|
||||
PurgeEvent::Error => Level::Error,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,27 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod channel;
|
||||
pub mod collector;
|
||||
pub mod conv;
|
||||
pub mod imple;
|
||||
pub mod macros;
|
||||
pub mod subscriber;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Error = Context<Cause, ERROR_CONTEXT_SIZE>;
|
||||
pub type Trace = Context<Event, TRACE_CONTEXT_SIZE>;
|
||||
|
||||
const ERROR_CONTEXT_SIZE: usize = 5;
|
||||
const TRACE_CONTEXT_SIZE: usize = 10;
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[repr(usize)]
|
||||
pub enum Level {
|
||||
Disable = 0,
|
||||
Trace = 1,
|
||||
Debug = 2,
|
||||
Info = 3,
|
||||
Warn = 4,
|
||||
Error = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum Value {
|
||||
@@ -62,18 +71,24 @@ pub enum Key {
|
||||
AccountId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum EventType {
|
||||
NewConnection,
|
||||
Error(Cause),
|
||||
SqlQuery,
|
||||
LdapQuery,
|
||||
PurgeTaskStarted,
|
||||
PurgeTaskRunning,
|
||||
PurgeTaskFinished,
|
||||
Purge(PurgeEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PurgeEvent {
|
||||
Started,
|
||||
Finished,
|
||||
Running,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Cause {
|
||||
Store(StoreCause),
|
||||
Jmap(JmapCause),
|
||||
@@ -82,7 +97,6 @@ pub enum Cause {
|
||||
Pop3,
|
||||
Smtp,
|
||||
Thread,
|
||||
Fetch,
|
||||
Acme,
|
||||
Dns,
|
||||
Ingest,
|
||||
@@ -90,100 +104,99 @@ pub enum Cause {
|
||||
Limit(LimitCause),
|
||||
Manage(ManageCause),
|
||||
Auth(AuthCause),
|
||||
Purge,
|
||||
Configuration,
|
||||
Resource(ResourceCause),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum StoreCause {
|
||||
AssertValue,
|
||||
BlobMissingMarker,
|
||||
FoundationDB,
|
||||
MySQL,
|
||||
PostgreSQL,
|
||||
RocksDB,
|
||||
SQLite,
|
||||
Ldap,
|
||||
ElasticSearch,
|
||||
Redis,
|
||||
S3,
|
||||
Filesystem,
|
||||
Pool,
|
||||
DataCorruption,
|
||||
Decompress,
|
||||
Deserialize,
|
||||
NotFound,
|
||||
NotConfigured,
|
||||
NotSupported,
|
||||
Unexpected,
|
||||
Crypto,
|
||||
AssertValue = 0,
|
||||
BlobMissingMarker = 1,
|
||||
FoundationDB = 2,
|
||||
MySQL = 3,
|
||||
PostgreSQL = 4,
|
||||
RocksDB = 5,
|
||||
SQLite = 6,
|
||||
Ldap = 7,
|
||||
ElasticSearch = 8,
|
||||
Redis = 9,
|
||||
S3 = 10,
|
||||
Filesystem = 11,
|
||||
Pool = 12,
|
||||
DataCorruption = 13,
|
||||
Decompress = 14,
|
||||
Deserialize = 15,
|
||||
NotFound = 16,
|
||||
NotConfigured = 17,
|
||||
NotSupported = 18,
|
||||
Unexpected = 19,
|
||||
Crypto = 20,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum JmapCause {
|
||||
// Method errors
|
||||
InvalidArguments,
|
||||
RequestTooLarge,
|
||||
StateMismatch,
|
||||
AnchorNotFound,
|
||||
UnsupportedFilter,
|
||||
UnsupportedSort,
|
||||
UnknownMethod,
|
||||
InvalidResultReference,
|
||||
Forbidden,
|
||||
AccountNotFound,
|
||||
AccountNotSupportedByMethod,
|
||||
AccountReadOnly,
|
||||
NotFound,
|
||||
CannotCalculateChanges,
|
||||
UnknownDataType,
|
||||
InvalidArguments = 0,
|
||||
RequestTooLarge = 1,
|
||||
StateMismatch = 2,
|
||||
AnchorNotFound = 3,
|
||||
UnsupportedFilter = 4,
|
||||
UnsupportedSort = 5,
|
||||
UnknownMethod = 6,
|
||||
InvalidResultReference = 7,
|
||||
Forbidden = 8,
|
||||
AccountNotFound = 9,
|
||||
AccountNotSupportedByMethod = 10,
|
||||
AccountReadOnly = 11,
|
||||
NotFound = 12,
|
||||
CannotCalculateChanges = 13,
|
||||
UnknownDataType = 14,
|
||||
|
||||
// Request errors
|
||||
UnknownCapability,
|
||||
NotJSON,
|
||||
NotRequest,
|
||||
UnknownCapability = 15,
|
||||
NotJSON = 16,
|
||||
NotRequest = 17,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum LimitCause {
|
||||
SizeRequest,
|
||||
SizeUpload,
|
||||
CallsIn,
|
||||
ConcurrentRequest,
|
||||
ConcurrentUpload,
|
||||
Quota,
|
||||
BlobQuota,
|
||||
TooManyRequests,
|
||||
SizeRequest = 0,
|
||||
SizeUpload = 1,
|
||||
CallsIn = 2,
|
||||
ConcurrentRequest = 3,
|
||||
ConcurrentUpload = 4,
|
||||
Quota = 5,
|
||||
BlobQuota = 6,
|
||||
TooManyRequests = 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ManageCause {
|
||||
MissingParameter,
|
||||
AlreadyExists,
|
||||
AssertFailed,
|
||||
NotFound,
|
||||
NotSupported,
|
||||
Error,
|
||||
MissingParameter = 0,
|
||||
AlreadyExists = 1,
|
||||
AssertFailed = 2,
|
||||
NotFound = 3,
|
||||
NotSupported = 4,
|
||||
Error = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum AuthCause {
|
||||
Failed,
|
||||
MissingTotp,
|
||||
TooManyAttempts,
|
||||
Banned,
|
||||
Error,
|
||||
Failed = 0,
|
||||
MissingTotp = 1,
|
||||
TooManyAttempts = 2,
|
||||
Banned = 3,
|
||||
Error = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ResourceCause {
|
||||
NotFound,
|
||||
BadParameters,
|
||||
Error,
|
||||
NotFound = 0,
|
||||
BadParameters = 1,
|
||||
Error = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Protocol {
|
||||
Jmap,
|
||||
Imap,
|
||||
@@ -194,10 +207,16 @@ pub enum Protocol {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Context<T, const N: usize> {
|
||||
inner: T,
|
||||
keys: [(Key, Value); N],
|
||||
keys_size: usize,
|
||||
pub struct Error {
|
||||
inner: Cause,
|
||||
keys: Vec<(Key, Value)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Event {
|
||||
inner: EventType,
|
||||
level: Level,
|
||||
keys: Vec<(Key, Value)>,
|
||||
}
|
||||
|
||||
pub trait AddContext<T> {
|
||||
|
||||
@@ -4,31 +4,51 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace {
|
||||
($event:ident $(, $key:ident = $value:expr)* $(,)?) => {
|
||||
{
|
||||
let event = $crate::Trace::new($crate::Event::$event)
|
||||
$(
|
||||
.ctx($crate::Key::$key, $crate::Value::from($value))
|
||||
)* ;
|
||||
|
||||
//eprintln!("{}", event);
|
||||
}
|
||||
};
|
||||
}
|
||||
// Helper macro to count the number of arguments
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($cause:ident $(, $key:ident = $value:expr)* $(,)?) => {{
|
||||
let event = $crate::Trace::new($crate::Event::Error($crate::Cause::$cause))
|
||||
.ctx($crate::Key::CausedBy, $crate::location!())
|
||||
$(
|
||||
.ctx($crate::Key::$key, $crate::Value::from($value))
|
||||
)* ;
|
||||
macro_rules! event {
|
||||
($event:ident($($param:expr),* $(,)?) $(, $key:ident = $value:expr)* $(,)?) => {
|
||||
{
|
||||
let et = $crate::EventType::$event($($param),*);
|
||||
let level = et.effective_level();
|
||||
if level.is_enabled() {
|
||||
$crate::Event::new(
|
||||
et,
|
||||
level,
|
||||
trc::__count!($($key)*)
|
||||
)
|
||||
$(
|
||||
.ctx($crate::Key::$key, $crate::Value::from($value))
|
||||
)*
|
||||
.send();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//eprintln!("{}", event);
|
||||
}};
|
||||
($event:ident $(, $key:ident = $value:expr)* $(,)?) => {
|
||||
{
|
||||
let et = $crate::EventType::$event;
|
||||
let level = et.effective_level();
|
||||
if level.is_enabled() {
|
||||
$crate::Event::new(
|
||||
et,
|
||||
level,
|
||||
trc::__count!($($key)*)
|
||||
)
|
||||
$(
|
||||
.ctx($crate::Key::$key, $crate::Value::from($value))
|
||||
)*
|
||||
.send();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! __count {
|
||||
() => (0usize);
|
||||
($head:tt $($tail:tt)*) => (1usize + trc::__count!($($tail)*));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||
110
crates/trc/src/subscriber.rs
Normal file
110
crates/trc/src/subscriber.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ahash::AHashSet;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::mpsc::{self, error::TrySendError};
|
||||
|
||||
use crate::{channel::ChannelError, Event, EventType, Level};
|
||||
|
||||
const MAX_BATCH_SIZE: usize = 32768;
|
||||
|
||||
pub(crate) static SUBSCRIBER_UPDATE: Mutex<Vec<Subscriber>> = Mutex::new(Vec::new());
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Subscriber {
|
||||
pub level: Level,
|
||||
pub disabled: AHashSet<EventType>,
|
||||
pub tx: mpsc::Sender<Vec<Arc<Event>>>,
|
||||
pub lossy: bool,
|
||||
pub batch: Vec<Arc<Event>>,
|
||||
}
|
||||
|
||||
pub struct SubscriberBuilder {
|
||||
pub level: Level,
|
||||
pub disabled: AHashSet<EventType>,
|
||||
pub lossy: bool,
|
||||
}
|
||||
|
||||
impl Subscriber {
|
||||
#[inline(always)]
|
||||
pub fn push_event(&mut self, trace: Arc<Event>) {
|
||||
if trace.level >= self.level && !self.disabled.contains(&trace.inner) {
|
||||
self.batch.push(trace);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_batch(&mut self) -> Result<(), ChannelError> {
|
||||
if !self.batch.is_empty() {
|
||||
match self.tx.try_send(std::mem::take(&mut self.batch)) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(TrySendError::Full(mut events)) => {
|
||||
if self.lossy && events.len() > MAX_BATCH_SIZE {
|
||||
events.retain(|e| e.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() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn with_level(mut self, level: Level) -> Self {
|
||||
self.level = level;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_disabled(mut self, disabled: impl IntoIterator<Item = EventType>) -> Self {
|
||||
self.disabled.extend(disabled);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_lossy(mut self, lossy: bool) -> Self {
|
||||
self.lossy = lossy;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register(self) -> mpsc::Receiver<Vec<Arc<Event>>> {
|
||||
let (tx, rx) = mpsc::channel(8192);
|
||||
|
||||
SUBSCRIBER_UPDATE.lock().push(Subscriber {
|
||||
level: self.level,
|
||||
disabled: self.disabled,
|
||||
tx,
|
||||
lossy: self.lossy,
|
||||
batch: Vec::new(),
|
||||
});
|
||||
|
||||
// Notify collector
|
||||
Event::new(EventType::Error(crate::Cause::Thread), Level::Info, 0).send();
|
||||
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SubscriberBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level: Level::Info,
|
||||
disabled: AHashSet::new(),
|
||||
lossy: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user