use crate::{LogClock, LogEvent, LogPolicy, LogSink, Sensitivity, Severity};
use std::borrow::Cow;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LogStats {
pub filtered: u64,
pub sink_failures: u64,
pub dropped: u64,
pub invalid: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SinkStats {
pub name: &'static str,
pub failures: u64,
}
struct SinkSlot {
sink: Arc<dyn LogSink>,
failures: AtomicU64,
}
pub struct LogDispatcher {
policy: LogPolicy,
sinks: Vec<SinkSlot>,
filtered: AtomicU64,
sink_failures: AtomicU64,
dropped: AtomicU64,
invalid: AtomicU64,
}
impl LogDispatcher {
pub fn new(policy: LogPolicy, sinks: Vec<Arc<dyn LogSink>>) -> Self {
Self {
policy,
sinks: sinks
.into_iter()
.map(|sink| SinkSlot {
sink,
failures: AtomicU64::new(0),
})
.collect(),
filtered: AtomicU64::new(0),
sink_failures: AtomicU64::new(0),
dropped: AtomicU64::new(0),
invalid: AtomicU64::new(0),
}
}
pub fn emit(&self, event: LogEvent) {
if self.sinks.is_empty() {
return;
}
if event.validate().is_err() {
self.invalid.fetch_add(1, Ordering::Relaxed);
return;
}
if !self.policy.allows(&event) {
self.filtered.fetch_add(1, Ordering::Relaxed);
return;
}
let severity = event.severity;
let sanitized = self.policy.sanitize_owned(event);
for slot in &self.sinks {
if self.policy.sensitivity == Sensitivity::Sensitive && !slot.sink.accepts_sensitive() {
self.sink_failures.fetch_add(1, Ordering::Relaxed);
slot.failures.fetch_add(1, Ordering::Relaxed);
continue;
}
if slot.sink.emit(&sanitized).is_err() {
self.sink_failures.fetch_add(1, Ordering::Relaxed);
slot.failures.fetch_add(1, Ordering::Relaxed);
if severity < Severity::Error {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
}
}
}
pub fn event<'a>(
&'a self,
timestamp_ms: u64,
component: impl Into<Cow<'a, str>>,
) -> LogBuilder<'a> {
LogBuilder {
dispatcher: self,
timestamp_ms,
component: component.into(),
verbosity: crate::Verbosity::V4,
}
}
pub fn event_now<'a>(
&'a self,
clock: &dyn LogClock,
component: impl Into<Cow<'a, str>>,
) -> LogBuilder<'a> {
self.event(clock.now_ms(), component)
}
pub fn enabled(&self, component: &str, verbosity: crate::Verbosity) -> bool {
!self.sinks.is_empty() && self.policy.allows_component(component, verbosity)
}
pub fn stats(&self) -> LogStats {
LogStats {
filtered: self.filtered.load(Ordering::Relaxed),
sink_failures: self.sink_failures.load(Ordering::Relaxed),
dropped: self.dropped.load(Ordering::Relaxed),
invalid: self.invalid.load(Ordering::Relaxed),
}
}
pub fn sink_stats(&self) -> Vec<SinkStats> {
self.sinks
.iter()
.map(|slot| SinkStats {
name: slot.sink.name(),
failures: slot.failures.load(Ordering::Relaxed),
})
.collect()
}
}
#[derive(Clone)]
pub struct LogBuilder<'a> {
dispatcher: &'a LogDispatcher,
timestamp_ms: u64,
component: Cow<'a, str>,
verbosity: crate::Verbosity,
}
impl<'a> LogBuilder<'a> {
#[must_use]
pub fn component(&self, component: impl Into<Cow<'a, str>>) -> Self {
Self {
component: component.into(),
..self.clone()
}
}
#[must_use]
pub fn verbosity(&self, verbosity: u8) -> Self {
Self {
verbosity: crate::Verbosity::new(verbosity).unwrap_or(crate::Verbosity::V4),
..self.clone()
}
}
pub fn try_verbosity(&self, verbosity: u8) -> Result<Self, crate::VerbosityError> {
let verbosity = crate::Verbosity::new(verbosity).ok_or(crate::VerbosityError)?;
Ok(Self {
verbosity,
..self.clone()
})
}
pub fn trace(&self, message: impl Into<String>) {
self.emit(Severity::Trace, message);
}
pub fn debug(&self, message: impl Into<String>) {
self.emit(Severity::Debug, message);
}
pub fn info(&self, message: impl Into<String>) {
self.emit(Severity::Info, message);
}
pub fn warn(&self, message: impl Into<String>) {
self.emit(Severity::Warn, message);
}
pub fn error(&self, message: impl Into<String>) {
self.emit(Severity::Error, message);
}
pub fn critical(&self, message: impl Into<String>) {
self.emit(Severity::Critical, message);
}
fn emit(&self, severity: Severity, message: impl Into<String>) {
if self.dispatcher.enabled(&self.component, self.verbosity) {
self.dispatcher.emit(LogEvent::new(
self.timestamp_ms,
severity,
self.verbosity,
self.component.to_string(),
message.into(),
));
}
}
}