//! The Abscissa component for Zebra's `tracing` implementation. use abscissa_core::{Component, FrameworkError, FrameworkErrorKind, Shutdown}; use tracing_error::ErrorLayer; use tracing_subscriber::{ fmt::Formatter, layer::SubscriberExt, reload::Handle, util::SubscriberInitExt, EnvFilter, FmtSubscriber, }; use crate::{application::app_version, config::TracingSection}; use super::flame; /// Abscissa component for initializing the `tracing` subsystem pub struct Tracing { filter_handle: Handle, flamegrapher: Option, } impl Tracing { /// Try to create a new [`Tracing`] component with the given `filter`. pub fn new(config: TracingSection) -> Result { let filter = config.filter.unwrap_or_else(|| "".to_string()); let flame_root = &config.flamegraph; // Only use color if tracing output is being sent to a terminal or if it was explicitly // forced to. let use_color = config.force_use_color || (config.use_color && atty::is(atty::Stream::Stdout)); // Construct a tracing subscriber with the supplied filter and enable reloading. let builder = FmtSubscriber::builder() .with_ansi(use_color) .with_env_filter(filter) .with_filter_reloading(); let filter_handle = builder.reload_handle(); let (flamelayer, flamegrapher) = if let Some(path) = flame_root { let (flamelayer, flamegrapher) = flame::layer(path); (Some(flamelayer), Some(flamegrapher)) } else { (None, None) }; let journaldlayer = if config.use_journald { let layer = tracing_journald::layer() .map_err(|e| FrameworkErrorKind::ComponentError.context(e))?; Some(layer) } else { None }; let subscriber = builder.finish().with(ErrorLayer::default()); #[cfg(feature = "enable-sentry")] let subscriber = subscriber.with(sentry_tracing::layer()); match (flamelayer, journaldlayer) { (None, None) => subscriber.init(), (Some(layer1), None) => subscriber.with(layer1).init(), (None, Some(layer2)) => subscriber.with(layer2).init(), (Some(layer1), Some(layer2)) => subscriber.with(layer1).with(layer2).init(), }; Ok(Self { filter_handle, flamegrapher, }) } /// Return the currently-active tracing filter. pub fn filter(&self) -> String { self.filter_handle .with_current(|filter| filter.to_string()) .expect("the subscriber is not dropped before the component is") } /// Reload the currently-active filter with the supplied value. /// /// This can be used to provide a dynamic tracing filter endpoint. pub fn reload_filter(&self, filter: impl Into) { self.filter_handle .reload(filter) .expect("the subscriber is not dropped before the component is"); } } impl std::fmt::Debug for Tracing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Tracing").finish() } } impl Component for Tracing { fn id(&self) -> abscissa_core::component::Id { abscissa_core::component::Id::new("zebrad::components::tracing::component::Tracing") } fn version(&self) -> abscissa_core::Version { app_version() } fn before_shutdown(&self, _kind: Shutdown) -> Result<(), FrameworkError> { if let Some(ref grapher) = self.flamegrapher { info!("writing flamegraph"); grapher .write_flamegraph() .map_err(|e| FrameworkErrorKind::ComponentError.context(e))? } Ok(()) } }