trace_tools/subscriber/
mod.rs

1// Copyright 2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4/// Contains layers used in this crate's subscriber for node diagnostics.
5pub mod layer;
6
7/// Contains visitors that record [`Span`](tracing::Span) field information.
8pub(crate) mod visitors;
9
10use crate::{util::Flamegrapher, Error};
11
12use fern_logger::LoggerConfig;
13
14use tracing_log::LogTracer;
15use tracing_subscriber::{
16    filter::{FilterFn, Filtered},
17    layer::Layered,
18    prelude::*,
19    Registry,
20};
21
22use std::path::{Path, PathBuf};
23
24/// Initialises a [`tracing_log::LogTracer`] that converts any incoming [`log`] records into [`tracing`] events,
25/// allowing subscribers to interact with log records.
26///
27/// This is necessary to perform logging through the [`log`] crate whilst also setting the global logger to a
28/// [`tracing::Subscriber`] implementation.
29///
30/// # Errors
31/// - Returns an [`Error`] if this function has failed to set the global logger.
32///
33/// # Notes
34///  - This should only be called once. Any subsequent calls will fail, since the global logger can only be set
35/// once in a program's lifespan.
36///  - If the global logger has already been set (by the [`log`] crate, for example), this will fail.
37pub fn collect_logs() -> Result<(), log::SetLoggerError> {
38    LogTracer::init()
39}
40
41type BaseSubscriber = Layered<
42    Filtered<
43        Option<layer::LogLayer>,
44        FilterFn,
45        Layered<Filtered<Option<layer::FlamegraphLayer>, FilterFn, Registry>, Registry>,
46    >,
47    Layered<Filtered<Option<layer::FlamegraphLayer>, FilterFn, Registry>, Registry>,
48>;
49
50/// [`Layered`](tracing_subscriber::layer::Layered) type describing the composition of the subscriber constructed
51/// by the [`trace_tools`](crate) crate.
52#[cfg(not(feature = "tokio-console"))]
53pub type TraceSubscriber = BaseSubscriber;
54
55/// [`Layered`](tracing_subscriber::layer::Layered) type describing the composition of the subscriber constructed
56/// by the [`trace_tools`](crate) crate.
57#[cfg(feature = "tokio-console")]
58pub type TraceSubscriber =
59    Layered<Filtered<Option<console_subscriber::ConsoleLayer>, FilterFn, BaseSubscriber>, BaseSubscriber>;
60
61/// Builder for the [`trace_tools`](crate) subscriber.
62///
63/// This can be used to enable/disable [`Layer`](tracing_subscriber::Layer)s provided by this crate.
64#[derive(Default)]
65#[must_use]
66pub struct SubscriberBuilder {
67    #[cfg(feature = "tokio-console")]
68    console_enabled: bool,
69
70    logger_config: Option<LoggerConfig>,
71    flamegraph_stack_file: Option<PathBuf>,
72}
73
74impl SubscriberBuilder {
75    /// Enables the [`LogLayer`](layer::LogLayer) for this subscriber, using the parameters provided by
76    /// the given [`LoggerConfig`].
77    pub fn with_log_layer(mut self, logger_config: LoggerConfig) -> Self {
78        self.logger_config = Some(logger_config);
79        self
80    }
81
82    /// Enables the [`LogLayer`](layer::LogLayer) for this subscriber, using the default configuration.
83    pub fn with_default_log_layer(mut self) -> Self {
84        self.logger_config = Some(LoggerConfig::default());
85        self
86    }
87
88    /// Enables the [`FlamegraphLayer`](layer::FlamegraphLayer) for this subscriber.
89    ///
90    /// The given path describes the desired output location of the folded stack file that is generated by
91    /// this layer during runtime. This file can then be used to produce a flamegraph by a [`Flamegrapher`]
92    /// instance, or by the [`inferno`] tool.
93    pub fn with_flamegraph_layer<P: AsRef<Path>>(mut self, folded_stack_file: P) -> Self {
94        self.flamegraph_stack_file = Some(folded_stack_file.as_ref().to_path_buf());
95        self
96    }
97
98    /// Enables the [`console_subscriber::ConsoleLayer`] for this subscriber.
99    #[cfg(feature = "tokio-console")]
100    pub fn with_console_layer(mut self) -> Self {
101        self.console_enabled = true;
102        self
103    }
104
105    /// Builds and returns the [`TraceSubscriber`].
106    ///
107    /// # Errors
108    ///  - Creation of the [`FlamegraphLayer`](layer::FlamegraphLayer) failed.
109    ///  - Creation of the [`LogLayer`](layer::LogLayer) failed.
110    ///
111    /// # Notes
112    ///  - This method calls the [`collect_logs`] function. Any [`log`] records emitted will be converting
113    /// into [`tracing`] events, and therefore any external functionality that deals with [`log`] records
114    /// may no longer function as expected.
115    ///  - This method does *not* set the global subscriber. As such, a call to `finish` can be used to
116    /// further extend the return subscriber with external [`Layer`](tracing_subscriber::Layer)s.
117    pub fn finish(self) -> Result<(TraceSubscriber, Option<Flamegrapher>), Error> {
118        self.compose()
119    }
120
121    /// Builds the [`TraceSubscriber`] and sets it as the global default subscriber.
122    ///
123    /// Returns a `Result` over an [`Option<Flamegrapher>`](Flamegrapher). The returned option is `Some` if
124    /// the [`LogLayer`](layer::LogLayer) is enabled and has been successfully initialised. If the
125    /// [`LogLayer](layer::LogLayer) has not been enabled with the builder, it is fine to ignore this value.
126    ///
127    /// # Errors
128    ///  - Creation of the [`FlamegraphLayer`](layer::FlamegraphLayer) failed.
129    ///  - Creation of the [`LogLayer`](layer::LogLayer) failed.
130    ///
131    /// # Panics
132    /// This method will panic if the flamegraph layer is enabled and the program is not built with
133    /// `--cfg tokio_unstable`.
134    ///
135    /// # Notes
136    ///  - This method calls the [`collect_logs`] function. Any [`log`] records emitted will be converting
137    /// into [`tracing`] events, and therefore any external functionality that deals with [`log`] records
138    /// may no longer function as expected.
139    ///  - This method sets the global subscriber. Any further attempts to set the global subscriber
140    /// (including another call to this method) will fail.
141    ///  - The subscriber initialised by this method cannot be extended.
142    pub fn init(self) -> Result<Option<Flamegrapher>, Error> {
143        let (subscriber, flamegrapher) = self.compose()?;
144
145        subscriber.init();
146
147        Ok(flamegrapher)
148    }
149
150    fn compose(mut self) -> Result<(TraceSubscriber, Option<Flamegrapher>), Error> {
151        let (flamegraph_layer, flamegrapher) = self.build_flamegraph_layer()?;
152        let log_layer = self.build_log_layer()?;
153
154        let subscriber = tracing_subscriber::registry()
155            .with(flamegraph_layer.with_filter(FilterFn::new(
156                layer::flamegraph_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
157            )))
158            .with(log_layer.with_filter(FilterFn::new(
159                layer::log_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
160            )));
161
162        #[cfg(feature = "tokio-console")]
163        {
164            let console_layer = if self.console_enabled {
165                Some(layer::console_layer()?)
166            } else {
167                None
168            };
169
170            let subscriber = subscriber.with(console_layer.with_filter(FilterFn::new(
171                layer::console_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
172            )));
173
174            Ok((subscriber, flamegrapher))
175        }
176
177        #[cfg(not(feature = "tokio-console"))]
178        Ok((subscriber, flamegrapher))
179    }
180
181    fn build_log_layer(&mut self) -> Result<Option<layer::LogLayer>, Error> {
182        if self.logger_config.is_some() {
183            collect_logs().map_err(|err| Error::LogLayer(err.into()))?;
184        }
185
186        self.logger_config
187            .take()
188            .map(layer::log_layer)
189            .map_or(Ok(None), |res| res.map(Some))
190    }
191
192    fn build_flamegraph_layer(&mut self) -> Result<(Option<layer::FlamegraphLayer>, Option<Flamegrapher>), Error> {
193        self.flamegraph_stack_file
194            .take()
195            .map_or(Ok((None, None)), |stack_file| {
196                layer::flamegraph_layer(stack_file).map(|(layer, flamegrapher)| (Some(layer), Some(flamegrapher)))
197            })
198    }
199}
200
201/// Returns a new, default [`SubscriberBuilder`].
202pub fn build() -> SubscriberBuilder {
203    SubscriberBuilder::default()
204}