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}