Skip to main content

tuwunel_core/log/capture/
layer.rs

1//! Tracing subscriber layer for ephemeral event captures.
2//!
3//! The layer selects active captures, records event fields, and invokes their
4//! callbacks synchronously.
5
6use std::{fmt, sync::Arc};
7
8use arrayvec::ArrayVec;
9use tracing::field::{Field, Visit};
10use tracing_core::{Event, Subscriber};
11use tracing_subscriber::{layer::Context, registry::LookupSpan};
12
13use super::{Capture, Data, State};
14
15/// Tracing subscriber layer that dispatches events to active captures.
16///
17/// The layer evaluates each capture's filter before recording fields and
18/// running its callback. Capture registration is shared through `State`.
19pub struct Layer {
20	state: Arc<State>,
21}
22
23struct Visitor {
24	values: Values,
25}
26
27type Values = ArrayVec<Value, 32>;
28/// Recorded tracing field name and formatted value.
29///
30/// Field names come from static callsite metadata. Values own their formatted
31/// text for the duration of callback delivery.
32pub type Value = (&'static str, String);
33
34type ScopeNames = ArrayVec<&'static str, 32>;
35
36impl Layer {
37	/// Creates a capture layer backed by shared registration state.
38	///
39	/// The shared state handle is cloned so captures registered through either
40	/// handle are visible to this subscriber layer.
41	#[inline]
42	pub fn new(state: &Arc<State>) -> Self { Self { state: state.clone() } }
43}
44
45impl fmt::Debug for Layer {
46	#[inline]
47	fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
48		formatter.debug_struct("capture::Layer").finish()
49	}
50}
51
52impl<S> tracing_subscriber::Layer<S> for Layer
53where
54	S: Subscriber + for<'a> LookupSpan<'a>,
55{
56	fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
57		self.state
58			.active
59			.read()
60			.expect("shared lock")
61			.iter()
62			.filter(|capture| filter(self, capture, event, &ctx))
63			.for_each(|capture| handle(self, capture, event, &ctx));
64	}
65}
66
67fn handle<S>(layer: &Layer, capture: &Capture, event: &Event<'_>, ctx: &Context<'_, S>)
68where
69	S: Subscriber + for<'a> LookupSpan<'a>,
70{
71	let names = ScopeNames::new();
72	let mut visitor = Visitor { values: Values::new() };
73	event.record(&mut visitor);
74
75	let mut closure = capture.closure.lock().expect("exclusive lock");
76	closure(Data {
77		layer,
78		event,
79		current: &ctx.current_span(),
80		values: &visitor.values,
81		scope: &names,
82	});
83}
84
85fn filter<S>(layer: &Layer, capture: &Capture, event: &Event<'_>, ctx: &Context<'_, S>) -> bool
86where
87	S: Subscriber + for<'a> LookupSpan<'a>,
88{
89	let values = Values::new();
90	let mut names = ScopeNames::new();
91	if let Some(scope) = ctx.event_scope(event) {
92		for span in scope {
93			names.push(span.name());
94		}
95	}
96
97	capture.filter.as_ref().is_none_or(|filter| {
98		filter(Data {
99			layer,
100			event,
101			current: &ctx.current_span(),
102			values: &values,
103			scope: &names,
104		})
105	})
106}
107
108impl Visit for Visitor {
109	fn record_debug(&mut self, f: &Field, v: &dyn fmt::Debug) {
110		self.values.push((f.name(), format!("{v:?}")));
111	}
112
113	fn record_str(&mut self, f: &Field, v: &str) { self.values.push((f.name(), v.to_owned())); }
114}