pub mod layer;
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};
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>,
>;
#[cfg(not(feature = "tokio-console"))]
pub type TraceSubscriber = BaseSubscriber;
#[cfg(feature = "tokio-console")]
pub type TraceSubscriber =
Layered<Filtered<Option<console_subscriber::ConsoleLayer>, FilterFn, BaseSubscriber>, BaseSubscriber>;
#[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 {
pub fn with_log_layer(mut self, logger_config: LoggerConfig) -> Self {
self.logger_config = Some(logger_config);
self
}
pub fn with_default_log_layer(mut self) -> Self {
self.logger_config = Some(LoggerConfig::default());
self
}
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
}
#[cfg(feature = "tokio-console")]
pub fn with_console_layer(mut self) -> Self {
self.console_enabled = true;
self
}
pub fn finish(self) -> Result<(TraceSubscriber, Option<Flamegrapher>), Error> {
self.compose()
}
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)))
})
}
}
pub fn build() -> SubscriberBuilder {
SubscriberBuilder::default()
}