1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

/// Contains layers used in this crate's subscriber for node diagnostics.
pub mod layer;

/// Contains visitors that record [`Span`](tracing::Span) field information.
pub(crate) mod visitors;

use crate::{util::Flamegrapher, Error};

use fern_logger::LoggerConfig;

use tracing_log::LogTracer;
use tracing_subscriber::{
    filter::{FilterFn, Filtered},
    layer::Layered,
    prelude::*,
    Registry,
};

use std::path::{Path, PathBuf};

/// Initialises a [`tracing_log::LogTracer`] that converts any incoming [`log`] records into [`tracing`] events,
/// allowing subscribers to interact with log records.
///
/// This is necessary to perform logging through the [`log`] crate whilst also setting the global logger to a
/// [`tracing::Subscriber`] implementation.
///
/// # Errors
/// - Returns an [`Error`] if this function has failed to set the global logger.
///
/// # Notes
///  - This should only be called once. Any subsequent calls will fail, since the global logger can only be set
/// once in a program's lifespan.
///  - If the global logger has already been set (by the [`log`] crate, for example), this will fail.
pub fn collect_logs() -> Result<(), log::SetLoggerError> {
    LogTracer::init()
}

type BaseSubscriber = Layered<
    Filtered<
        Option<layer::LogLayer>,
        FilterFn,
        Layered<Filtered<Option<layer::FlamegraphLayer>, FilterFn, Registry>, Registry>,
    >,
    Layered<Filtered<Option<layer::FlamegraphLayer>, FilterFn, Registry>, Registry>,
>;

/// [`Layered`](tracing_subscriber::layer::Layered) type describing the composition of the subscriber constructed
/// by the [`trace_tools`](crate) crate.
#[cfg(not(feature = "tokio-console"))]
pub type TraceSubscriber = BaseSubscriber;

/// [`Layered`](tracing_subscriber::layer::Layered) type describing the composition of the subscriber constructed
/// by the [`trace_tools`](crate) crate.
#[cfg(feature = "tokio-console")]
pub type TraceSubscriber =
    Layered<Filtered<Option<console_subscriber::ConsoleLayer>, FilterFn, BaseSubscriber>, BaseSubscriber>;

/// Builder for the [`trace_tools`](crate) subscriber.
///
/// This can be used to enable/disable [`Layer`](tracing_subscriber::Layer)s provided by this crate.
#[derive(Default)]
#[must_use]
pub struct SubscriberBuilder {
    #[cfg(feature = "tokio-console")]
    console_enabled: bool,

    logger_config: Option<LoggerConfig>,
    flamegraph_stack_file: Option<PathBuf>,
}

impl SubscriberBuilder {
    /// Enables the [`LogLayer`](layer::LogLayer) for this subscriber, using the parameters provided by
    /// the given [`LoggerConfig`].
    pub fn with_log_layer(mut self, logger_config: LoggerConfig) -> Self {
        self.logger_config = Some(logger_config);
        self
    }

    /// Enables the [`LogLayer`](layer::LogLayer) for this subscriber, using the default configuration.
    pub fn with_default_log_layer(mut self) -> Self {
        self.logger_config = Some(LoggerConfig::default());
        self
    }

    /// Enables the [`FlamegraphLayer`](layer::FlamegraphLayer) for this subscriber.
    ///
    /// The given path describes the desired output location of the folded stack file that is generated by
    /// this layer during runtime. This file can then be used to produce a flamegraph by a [`Flamegrapher`]
    /// instance, or by the [`inferno`] tool.
    pub fn with_flamegraph_layer<P: AsRef<Path>>(mut self, folded_stack_file: P) -> Self {
        self.flamegraph_stack_file = Some(folded_stack_file.as_ref().to_path_buf());
        self
    }

    /// Enables the [`console_subscriber::ConsoleLayer`] for this subscriber.
    #[cfg(feature = "tokio-console")]
    pub fn with_console_layer(mut self) -> Self {
        self.console_enabled = true;
        self
    }

    /// Builds and returns the [`TraceSubscriber`].
    ///
    /// # Errors
    ///  - Creation of the [`FlamegraphLayer`](layer::FlamegraphLayer) failed.
    ///  - Creation of the [`LogLayer`](layer::LogLayer) failed.
    ///
    /// # Notes
    ///  - This method calls the [`collect_logs`] function. Any [`log`] records emitted will be converting
    /// into [`tracing`] events, and therefore any external functionality that deals with [`log`] records
    /// may no longer function as expected.
    ///  - This method does *not* set the global subscriber. As such, a call to `finish` can be used to
    /// further extend the return subscriber with external [`Layer`](tracing_subscriber::Layer)s.
    pub fn finish(self) -> Result<(TraceSubscriber, Option<Flamegrapher>), Error> {
        self.compose()
    }

    /// Builds the [`TraceSubscriber`] and sets it as the global default subscriber.
    ///
    /// Returns a `Result` over an [`Option<Flamegrapher>`](Flamegrapher). The returned option is `Some` if
    /// the [`LogLayer`](layer::LogLayer) is enabled and has been successfully initialised. If the
    /// [`LogLayer](layer::LogLayer) has not been enabled with the builder, it is fine to ignore this value.
    ///
    /// # Errors
    ///  - Creation of the [`FlamegraphLayer`](layer::FlamegraphLayer) failed.
    ///  - Creation of the [`LogLayer`](layer::LogLayer) failed.
    ///
    /// # Panics
    /// This method will panic if the flamegraph layer is enabled and the program is not built with
    /// `--cfg tokio_unstable`.
    ///
    /// # Notes
    ///  - This method calls the [`collect_logs`] function. Any [`log`] records emitted will be converting
    /// into [`tracing`] events, and therefore any external functionality that deals with [`log`] records
    /// may no longer function as expected.
    ///  - This method sets the global subscriber. Any further attempts to set the global subscriber
    /// (including another call to this method) will fail.
    ///  - The subscriber initialised by this method cannot be extended.
    pub fn init(self) -> Result<Option<Flamegrapher>, Error> {
        let (subscriber, flamegrapher) = self.compose()?;

        subscriber.init();

        Ok(flamegrapher)
    }

    fn compose(mut self) -> Result<(TraceSubscriber, Option<Flamegrapher>), Error> {
        let (flamegraph_layer, flamegrapher) = self.build_flamegraph_layer()?;
        let log_layer = self.build_log_layer()?;

        let subscriber = tracing_subscriber::registry()
            .with(flamegraph_layer.with_filter(FilterFn::new(
                layer::flamegraph_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
            )))
            .with(log_layer.with_filter(FilterFn::new(
                layer::log_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
            )));

        #[cfg(feature = "tokio-console")]
        {
            let console_layer = if self.console_enabled {
                Some(layer::console_layer()?)
            } else {
                None
            };

            let subscriber = subscriber.with(console_layer.with_filter(FilterFn::new(
                layer::console_filter as for<'r, 's> fn(&'r tracing::Metadata<'s>) -> bool,
            )));

            Ok((subscriber, flamegrapher))
        }

        #[cfg(not(feature = "tokio-console"))]
        Ok((subscriber, flamegrapher))
    }

    fn build_log_layer(&mut self) -> Result<Option<layer::LogLayer>, Error> {
        if self.logger_config.is_some() {
            collect_logs().map_err(|err| Error::LogLayer(err.into()))?;
        }

        self.logger_config
            .take()
            .map(layer::log_layer)
            .map_or(Ok(None), |res| res.map(Some))
    }

    fn build_flamegraph_layer(&mut self) -> Result<(Option<layer::FlamegraphLayer>, Option<Flamegrapher>), Error> {
        self.flamegraph_stack_file
            .take()
            .map_or(Ok((None, None)), |stack_file| {
                layer::flamegraph_layer(stack_file).map(|(layer, flamegrapher)| (Some(layer), Some(flamegrapher)))
            })
    }
}

/// Returns a new, default [`SubscriberBuilder`].
pub fn build() -> SubscriberBuilder {
    SubscriberBuilder::default()
}