tracing_forest/layer/
mod.rs

1use crate::fail;
2use crate::printer::{PrettyPrinter, TestCapturePrinter};
3use crate::processor::{Processor, Sink};
4use crate::tag::{NoTag, Tag, TagParser};
5use crate::tree::{self, FieldSet, Tree};
6#[cfg(feature = "chrono")]
7use chrono::Utc;
8use std::fmt;
9use std::io::{self, Write};
10use std::time::Instant;
11use tracing::field::{Field, Visit};
12use tracing::span::{Attributes, Id};
13use tracing::{Event, Subscriber};
14use tracing_subscriber::layer::{Context, Layer, SubscriberExt};
15use tracing_subscriber::registry::{LookupSpan, Registry, SpanRef};
16use tracing_subscriber::util::SubscriberInitExt;
17use tracing_subscriber::util::TryInitError;
18#[cfg(feature = "uuid")]
19use uuid::Uuid;
20#[cfg(feature = "uuid")]
21pub(crate) mod id;
22
23pub(crate) struct OpenedSpan {
24    span: tree::Span,
25    start: Instant,
26}
27
28impl OpenedSpan {
29    fn new<S>(attrs: &Attributes, _ctx: &Context<S>) -> Self
30    where
31        S: Subscriber + for<'a> LookupSpan<'a>,
32    {
33        let mut fields = FieldSet::default();
34        #[cfg(feature = "uuid")]
35        let mut maybe_uuid = None;
36
37        attrs.record(&mut |field: &Field, value: &dyn fmt::Debug| {
38            #[cfg(feature = "uuid")]
39            if field.name() == "uuid" && maybe_uuid.is_none() {
40                const LENGTH: usize = 45;
41                let mut buf = [0u8; LENGTH];
42                let mut remaining = &mut buf[..];
43
44                if let Ok(()) = write!(remaining, "{:?}", value) {
45                    let len = LENGTH - remaining.len();
46                    if let Some(parsed) = id::try_parse(&buf[..len]) {
47                        maybe_uuid = Some(parsed);
48                    }
49                }
50                return;
51            }
52
53            let value = format!("{:?}", value);
54            fields.push(tree::Field::new(field.name(), value));
55        });
56
57        let shared = tree::Shared {
58            #[cfg(feature = "chrono")]
59            timestamp: Utc::now(),
60            level: *attrs.metadata().level(),
61            fields,
62            #[cfg(feature = "uuid")]
63            uuid: maybe_uuid.unwrap_or_else(|| match _ctx.lookup_current() {
64                Some(parent) => parent
65                    .extensions()
66                    .get::<OpenedSpan>()
67                    .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
68                    .span
69                    .uuid(),
70                None => Uuid::new_v4(),
71            }),
72        };
73
74        OpenedSpan {
75            span: tree::Span::new(shared, attrs.metadata().name()),
76            start: Instant::now(),
77        }
78    }
79
80    fn enter(&mut self) {
81        self.start = Instant::now();
82    }
83
84    fn exit(&mut self) {
85        self.span.total_duration += self.start.elapsed();
86    }
87
88    fn close(self) -> tree::Span {
89        self.span
90    }
91
92    fn record_event(&mut self, event: tree::Event) {
93        #[cfg(feature = "uuid")]
94        let event = {
95            let mut event = event;
96            event.shared.uuid = self.span.uuid();
97            event
98        };
99
100        self.span.nodes.push(Tree::Event(event));
101    }
102
103    fn record_span(&mut self, span: tree::Span) {
104        self.span.inner_duration += span.total_duration();
105        self.span.nodes.push(Tree::Span(span));
106    }
107
108    #[cfg(feature = "uuid")]
109    pub(crate) fn uuid(&self) -> Uuid {
110        self.span.uuid()
111    }
112}
113
114/// A [`Layer`] that collects and processes trace data while preserving
115/// contextual coherence.
116#[derive(Clone, Debug)]
117pub struct ForestLayer<P, T> {
118    processor: P,
119    tag: T,
120}
121
122impl<P: Processor, T: TagParser> ForestLayer<P, T> {
123    /// Create a new `ForestLayer` from a [`Processor`] and a [`TagParser`].
124    pub fn new(processor: P, tag: T) -> Self {
125        ForestLayer { processor, tag }
126    }
127}
128
129impl<P: Processor> From<P> for ForestLayer<P, NoTag> {
130    fn from(processor: P) -> Self {
131        ForestLayer::new(processor, NoTag)
132    }
133}
134
135impl ForestLayer<Sink, NoTag> {
136    /// Create a new `ForestLayer` that does nothing with collected trace data.
137    pub fn sink() -> Self {
138        ForestLayer::from(Sink)
139    }
140}
141
142impl Default for ForestLayer<PrettyPrinter, NoTag> {
143    fn default() -> Self {
144        ForestLayer {
145            processor: PrettyPrinter::new(),
146            tag: NoTag,
147        }
148    }
149}
150
151impl<P, T, S> Layer<S> for ForestLayer<P, T>
152where
153    P: Processor,
154    T: TagParser,
155    S: Subscriber + for<'a> LookupSpan<'a>,
156{
157    fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
158        let span = ctx.span(id).expect(fail::SPAN_NOT_IN_CONTEXT);
159        let opened = OpenedSpan::new(attrs, &ctx);
160
161        let mut extensions = span.extensions_mut();
162        extensions.insert(opened);
163    }
164
165    fn on_event(&self, event: &Event, ctx: Context<S>) {
166        struct Visitor {
167            message: Option<String>,
168            fields: FieldSet,
169            immediate: bool,
170        }
171
172        impl Visit for Visitor {
173            fn record_bool(&mut self, field: &Field, value: bool) {
174                match field.name() {
175                    "immediate" => self.immediate |= value,
176                    _ => self.record_debug(field, &value),
177                }
178            }
179
180            fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
181                let value = format!("{:?}", value);
182                match field.name() {
183                    "message" if self.message.is_none() => self.message = Some(value),
184                    key => self.fields.push(tree::Field::new(key, value)),
185                }
186            }
187        }
188
189        let mut visitor = Visitor {
190            message: None,
191            fields: FieldSet::default(),
192            immediate: false,
193        };
194
195        event.record(&mut visitor);
196
197        let shared = tree::Shared {
198            #[cfg(feature = "uuid")]
199            uuid: Uuid::nil(),
200            #[cfg(feature = "chrono")]
201            timestamp: Utc::now(),
202            level: *event.metadata().level(),
203            fields: visitor.fields,
204        };
205
206        let tree_event = tree::Event {
207            shared,
208            message: visitor.message,
209            tag: self.tag.parse(event),
210        };
211
212        let current_span = ctx.event_span(event);
213
214        if visitor.immediate {
215            write_immediate(&tree_event, current_span.as_ref()).expect("writing urgent failed");
216        }
217
218        match current_span.as_ref() {
219            Some(parent) => parent
220                .extensions_mut()
221                .get_mut::<OpenedSpan>()
222                .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
223                .record_event(tree_event),
224            None => self
225                .processor
226                .process(Tree::Event(tree_event))
227                .expect(fail::PROCESSING_ERROR),
228        }
229    }
230
231    fn on_enter(&self, id: &Id, ctx: Context<S>) {
232        ctx.span(id)
233            .expect(fail::SPAN_NOT_IN_CONTEXT)
234            .extensions_mut()
235            .get_mut::<OpenedSpan>()
236            .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
237            .enter();
238    }
239
240    fn on_exit(&self, id: &Id, ctx: Context<S>) {
241        ctx.span(id)
242            .expect(fail::SPAN_NOT_IN_CONTEXT)
243            .extensions_mut()
244            .get_mut::<OpenedSpan>()
245            .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
246            .exit();
247    }
248
249    fn on_close(&self, id: Id, ctx: Context<S>) {
250        let span_ref = ctx.span(&id).expect(fail::SPAN_NOT_IN_CONTEXT);
251
252        let mut span = span_ref
253            .extensions_mut()
254            .remove::<OpenedSpan>()
255            .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
256            .close();
257
258        // Ensure that the total duration is at least as much as the inner
259        // duration. This is caused by when a child span is manually passed
260        // a parent span and then enters without entering the parent span. Also
261        // when a child span is created within a parent, and then stored and
262        // entered again when the parent isn't opened.
263        //
264        // Issue: https://github.com/QnnOkabayashi/tracing-forest/issues/11
265        if span.total_duration < span.inner_duration {
266            span.total_duration = span.inner_duration;
267        }
268
269        match span_ref.parent() {
270            Some(parent) => parent
271                .extensions_mut()
272                .get_mut::<OpenedSpan>()
273                .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
274                .record_span(span),
275            None => self
276                .processor
277                .process(Tree::Span(span))
278                .expect(fail::PROCESSING_ERROR),
279        }
280    }
281}
282
283fn write_immediate<S>(event: &tree::Event, current: Option<&SpanRef<S>>) -> io::Result<()>
284where
285    S: for<'a> LookupSpan<'a>,
286{
287    // uuid timestamp LEVEL root > inner > leaf > my message here | key: val
288    #[cfg(feature = "smallvec")]
289    let mut writer = smallvec::SmallVec::<[u8; 256]>::new();
290    #[cfg(not(feature = "smallvec"))]
291    let mut writer = Vec::with_capacity(256);
292
293    #[cfg(feature = "uuid")]
294    if let Some(span) = current {
295        let uuid = span
296            .extensions()
297            .get::<OpenedSpan>()
298            .expect(fail::OPENED_SPAN_NOT_IN_EXTENSIONS)
299            .span
300            .uuid();
301        write!(writer, "{} ", uuid)?;
302    }
303
304    #[cfg(feature = "chrono")]
305    write!(writer, "{} ", event.timestamp().to_rfc3339())?;
306
307    write!(writer, "{:<8} ", event.level())?;
308
309    let tag = event.tag().unwrap_or_else(|| Tag::from(event.level()));
310
311    write!(writer, "{icon} IMMEDIATE {icon} ", icon = tag.icon())?;
312
313    if let Some(span) = current {
314        for ancestor in span.scope().from_root() {
315            write!(writer, "{} > ", ancestor.name())?;
316        }
317    }
318
319    // we should just do pretty printing here.
320
321    if let Some(message) = event.message() {
322        write!(writer, "{}", message)?;
323    }
324
325    for field in event.fields().iter() {
326        write!(writer, " | {}: {}", field.key(), field.value())?;
327    }
328
329    writeln!(writer)?;
330
331    io::stderr().write_all(&writer)
332}
333
334/// Initializes a global subscriber with a [`ForestLayer`] using the default configuration.
335///
336/// This function is intended for quick initialization and processes log trees "inline",
337/// meaning it doesn't take advantage of a worker task for formatting and writing.
338/// To use a worker task, consider using the [`worker_task`] function. Alternatively,
339/// configure a `Subscriber` manually using a `ForestLayer`.
340///
341/// [`worker_task`]: crate::runtime::worker_task
342///
343/// # Examples
344/// ```
345/// use tracing::{info, info_span};
346///
347/// tracing_forest::init();
348///
349/// info!("Hello, world!");
350/// info_span!("my_span").in_scope(|| {
351///     info!("Relevant information");
352/// });
353/// ```
354/// Produces the the output:
355/// ```log
356/// INFO     i [info]: Hello, world!
357/// INFO     my_span [ 26.0µs | 100.000% ]
358/// INFO     ┕━ i [info]: Relevant information
359/// ```
360pub fn init() {
361    Registry::default().with(ForestLayer::default()).init();
362}
363
364/// Initializes a global subscriber for cargo tests with a [`ForestLayer`] using the default
365/// configuration.
366///
367/// This function is intended for test case initialization and processes log trees "inline",
368/// meaning it doesn't take advantage of a worker task for formatting and writing.
369///
370/// [`worker_task`]: crate::runtime::worker_task
371///
372/// # Examples
373/// ```
374/// use tracing::{info, info_span};
375///
376/// let _ = tracing_forest::test_init();
377///
378/// info!("Hello, world!");
379/// info_span!("my_span").in_scope(|| {
380///     info!("Relevant information");
381/// });
382/// ```
383pub fn test_init() -> Result<(), TryInitError> {
384    Registry::default()
385        .with(ForestLayer::new(TestCapturePrinter::new(), NoTag))
386        .try_init()
387}