tracing capture interface

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-06-11 01:26:31 +00:00
parent 1bb4021b90
commit aa34021b27
15 changed files with 284 additions and 27 deletions

View file

@ -0,0 +1,31 @@
use tracing::Level;
use tracing_core::{span::Current, Event};
use super::layer::Value;
pub struct Data<'a> {
pub event: &'a Event<'a>,
pub current: &'a Current,
pub values: Option<&'a mut [Value]>,
}
impl Data<'_> {
#[must_use]
pub fn level(&self) -> Level { *self.event.metadata().level() }
#[must_use]
pub fn mod_name(&self) -> &str { self.event.metadata().module_path().unwrap_or_default() }
#[must_use]
pub fn span_name(&self) -> &str { self.current.metadata().map_or("", |s| s.name()) }
#[must_use]
pub fn message(&self) -> &str {
self.values
.as_ref()
.expect("values are not composed for a filter")
.iter()
.find(|(k, _)| *k == "message")
.map_or("", |(_, v)| v.as_str())
}
}

View file

@ -0,0 +1,12 @@
use std::sync::Arc;
use super::Capture;
/// Capture instance scope guard.
pub struct Guard {
pub(super) capture: Arc<Capture>,
}
impl Drop for Guard {
fn drop(&mut self) { self.capture.stop(); }
}

View file

@ -0,0 +1,82 @@
use std::{fmt, sync::Arc};
use tracing::field::{Field, Visit};
use tracing_core::{Event, Subscriber};
use tracing_subscriber::{layer::Context, registry::LookupSpan};
use super::{Capture, Data, State};
pub type Value = (&'static str, String);
pub struct Layer {
state: Arc<State>,
}
struct Visitor {
values: Vec<Value>,
}
impl Layer {
pub fn new(state: &Arc<State>) -> Self {
Self {
state: state.clone(),
}
}
}
impl fmt::Debug for Layer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("capture::Layer").finish()
}
}
impl<S> tracing_subscriber::Layer<S> for Layer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
self.state
.active
.read()
.expect("shared lock")
.iter()
.filter(|capture| filter(capture, event, &ctx))
.for_each(|capture| handle(capture, event, &ctx));
}
}
fn handle<S>(capture: &Capture, event: &Event<'_>, ctx: &Context<'_, S>)
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let mut visitor = Visitor {
values: Vec::new(),
};
event.record(&mut visitor);
let mut closure = capture.closure.lock().expect("exclusive lock");
closure(Data {
event,
current: &ctx.current_span(),
values: Some(&mut visitor.values),
});
}
fn filter<S>(capture: &Capture, event: &Event<'_>, ctx: &Context<'_, S>) -> bool
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
capture.filter.as_ref().map_or(true, |filter| {
filter(Data {
event,
current: &ctx.current_span(),
values: None,
})
})
}
impl Visit for Visitor {
fn record_debug(&mut self, f: &Field, v: &dyn fmt::Debug) { self.values.push((f.name(), format!("{v:?}"))); }
fn record_str(&mut self, f: &Field, v: &str) { self.values.push((f.name(), v.to_owned())); }
}

View file

@ -0,0 +1,50 @@
pub mod data;
mod guard;
pub mod layer;
pub mod state;
pub mod util;
use std::sync::{Arc, Mutex};
pub use data::Data;
use guard::Guard;
pub use layer::{Layer, Value};
pub use state::State;
pub use util::to_html;
pub type Filter = dyn Fn(Data<'_>) -> bool + Send + Sync + 'static;
pub type Closure = dyn FnMut(Data<'_>) + Send + Sync + 'static;
/// Capture instance state.
pub struct Capture {
state: Arc<State>,
filter: Option<Box<Filter>>,
closure: Mutex<Box<Closure>>,
}
impl Capture {
/// Construct a new capture instance. Capture does not start until the Guard
/// is in scope.
#[must_use]
pub fn new<F, C>(state: &Arc<State>, filter: Option<F>, closure: C) -> Arc<Self>
where
F: Fn(Data<'_>) -> bool + Send + Sync + 'static,
C: FnMut(Data<'_>) + Send + Sync + 'static,
{
Arc::new(Self {
state: state.clone(),
filter: filter.map(|p| -> Box<Filter> { Box::new(p) }),
closure: Mutex::new(Box::new(closure)),
})
}
#[must_use]
pub fn start(self: &Arc<Self>) -> Guard {
self.state.add(self);
Guard {
capture: self.clone(),
}
}
pub fn stop(self: &Arc<Self>) { self.state.del(self); }
}

View file

@ -0,0 +1,35 @@
use std::sync::{Arc, RwLock};
use super::Capture;
/// Capture layer state.
pub struct State {
pub(super) active: RwLock<Vec<Arc<Capture>>>,
}
impl Default for State {
fn default() -> Self { Self::new() }
}
impl State {
#[must_use]
pub fn new() -> Self {
Self {
active: RwLock::new(Vec::new()),
}
}
pub(super) fn add(&self, capture: &Arc<Capture>) {
self.active
.write()
.expect("locked for writing")
.push(capture.clone());
}
pub(super) fn del(&self, capture: &Arc<Capture>) {
let mut vec = self.active.write().expect("locked for writing");
if let Some(pos) = vec.iter().position(|v| Arc::ptr_eq(v, capture)) {
vec.swap_remove(pos);
}
}
}

View file

@ -0,0 +1,19 @@
use std::sync::{Arc, Mutex};
use super::{super::fmt, Closure};
pub fn to_html<S>(out: &Arc<Mutex<S>>) -> Box<Closure>
where
S: std::fmt::Write + Send + 'static,
{
let out = out.clone();
Box::new(move |data| {
fmt::html(
&mut *out.lock().expect("locked"),
&data.level(),
data.span_name(),
data.message(),
)
.expect("log line appended");
})
}