1#![doc(html_logo_url = "https://cdn.floofy.dev/images/trans.png")]
27#![doc(html_favicon_url = "https://cdn.floofy.dev/images/trans.png")]
28#![cfg_attr(any(noeldoc, docsrs), feature(doc_cfg))]
29
30#[cfg(feature = "writers")]
31#[cfg_attr(any(docsrs, noeldoc), doc(cfg(feature = "writers")))]
32pub mod writers;
33
34#[cfg(not(feature = "writers"))]
35mod writers;
36
37#[cfg(not(feature = "writers"))]
38pub use writers::JsonVisitor;
39
40use std::{io::Write, sync::RwLock};
41use tracing::{span, Event, Metadata, Subscriber};
42use tracing_subscriber::{
43 registry::{LookupSpan, SpanRef},
44 Layer,
45};
46
47pub trait WriteFn<S: for<'l> LookupSpan<'l>>: Send {
54 fn buffer(&self, event: &Event, metadata: &Metadata, spans: Vec<SpanRef<'_, S>>) -> String;
55}
56
57impl<S: for<'l> LookupSpan<'l>, F> WriteFn<S> for F
58where
59 F: Fn(&Event, &Metadata, Vec<SpanRef<'_, S>>) -> String + Send,
60{
61 fn buffer(&self, event: &Event, metadata: &Metadata, spans: Vec<SpanRef<'_, S>>) -> String {
62 (self)(event, metadata, spans)
63 }
64}
65
66pub struct WriteLayer<S: for<'l> LookupSpan<'l>> {
69 writer: RwLock<Box<dyn Write + Send + Sync>>,
70 write_fn: Option<Box<dyn WriteFn<S> + Send + Sync>>,
71}
72
73impl<S: for<'l> LookupSpan<'l>> WriteLayer<S> {
74 pub fn new<W: Write + Send + Sync + 'static>(writer: W) -> WriteLayer<S> {
76 WriteLayer {
77 writer: RwLock::new(Box::new(writer)),
78 write_fn: None,
79 }
80 }
81
82 pub fn new_with<W: Write + Send + Sync + 'static, F: WriteFn<S> + Send + Sync + 'static>(
84 writer: W,
85 fn_: F,
86 ) -> WriteLayer<S> {
87 WriteLayer {
88 writer: RwLock::new(Box::new(writer)),
89 write_fn: Some(Box::new(fn_)),
90 }
91 }
92}
93
94#[derive(Debug)]
95pub(crate) struct JsonExtension(pub(crate) std::collections::BTreeMap<String, serde_json::Value>);
96impl<S: Subscriber + for<'l> LookupSpan<'l>> Layer<S> for WriteLayer<S> {
97 fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
98 let span = ctx.span(id).unwrap();
99 let mut data = std::collections::BTreeMap::new();
100
101 let mut visitor = crate::writers::JsonVisitor(&mut data);
102 attrs.record(&mut visitor);
103
104 span.extensions_mut().insert(JsonExtension(data));
105 }
106
107 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
108 let span = ctx.span(span).unwrap();
109 let mut exts = span.extensions_mut();
110 let data: &mut JsonExtension = exts.get_mut::<JsonExtension>().unwrap();
111
112 let mut visitor = crate::writers::JsonVisitor(&mut data.0);
113 values.record(&mut visitor);
114 }
115
116 fn on_event(&self, event: &Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
117 let mut writer = self.writer.write().unwrap();
118 if let Some(ref fn_) = self.write_fn {
119 cfg_if::cfg_if! {
120 if #[cfg(feature = "tracing-log")] {
121 use tracing_log::NormalizeEvent;
122
123 let metadata = event.normalized_metadata();
124 let metadata = metadata.as_ref().unwrap_or_else(|| event.metadata());
125 } else {
126 let metadata = event.metadata();
127 }
128 };
129
130 let mut spans: Vec<SpanRef<'_, S>> = vec![];
131 if let Some(scope) = ctx.event_scope(event) {
132 for span in scope.from_root() {
133 spans.push(span);
134 }
135 }
136
137 let buf = fn_.buffer(event, metadata, spans);
138 let _ = write!(writer, "{buf}");
139 let _ = writeln!(writer);
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::WriteLayer;
147 use std::io;
148 use tracing::Dispatch;
149 use tracing_subscriber::{layer::SubscriberExt, registry, Layer, Registry};
150
151 fn __assert_is_layer<S>(_: &dyn Layer<S>) {
152 }
154
155 fn __assert_is_dispatchable(_: impl Into<Dispatch>) {
156 }
158
159 #[test]
160 fn assertions() {
161 __assert_is_layer::<Registry>(&WriteLayer::new(io::stdout()));
162 __assert_is_dispatchable(registry().with(WriteLayer::new(io::stdout())));
163
164 #[cfg(feature = "writers")]
165 __assert_is_dispatchable(registry().with(WriteLayer::new_with(
166 io::stdout(),
167 crate::writers::default::Writer::default(),
168 )));
169
170 #[cfg(feature = "writers")]
171 __assert_is_dispatchable(registry().with(WriteLayer::new_with(io::stdout(), crate::writers::json)));
172 }
173}