diff --git a/Cargo.lock b/Cargo.lock index dbaad3473..30ae55f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3272,6 +3272,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", + "rstest", "snafu 0.8.5", "stackable-webhook", "tokio", diff --git a/crates/stackable-telemetry/Cargo.toml b/crates/stackable-telemetry/Cargo.toml index 7d1c5d03d..e1de94107 100644 --- a/crates/stackable-telemetry/Cargo.toml +++ b/crates/stackable-telemetry/Cargo.toml @@ -25,6 +25,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } [dev-dependencies] tokio.workspace = true tracing-opentelemetry.workspace = true +rstest.workspace = true stackable-webhook = { path = "../stackable-webhook" } [package.metadata.cargo-udeps.ignore] diff --git a/crates/stackable-telemetry/src/tracing/mod.rs b/crates/stackable-telemetry/src/tracing/mod.rs index 43c943599..8493d8106 100644 --- a/crates/stackable-telemetry/src/tracing/mod.rs +++ b/crates/stackable-telemetry/src/tracing/mod.rs @@ -18,7 +18,7 @@ use snafu::{ResultExt as _, Snafu}; use tracing::subscriber::SetGlobalDefaultError; use tracing_subscriber::{filter::Directive, layer::SubscriberExt, EnvFilter, Layer, Registry}; -use settings::{ConsoleLogSettings, OtlpLogSettings, OtlpTraceSettings}; +use settings::*; pub mod settings; @@ -40,11 +40,46 @@ pub enum Error { SetGlobalDefaultSubscriber { source: SetGlobalDefaultError }, } -/// Easily initialize a set of preconfigured [`Subscriber`][1] layers. +/// Easily initialize a set of pre-configured [`Subscriber`][1] layers. /// -/// # Usage: +/// # Usage +/// +/// There are two different styles to configure individual subscribers: Using the sophisticated +/// [`SettingsBuilder`] or the simplified tuple style for basic configuration. Currently, three +/// different subscribers are supported: console output, OTLP log export, and OTLP trace export. +/// +/// The subscribers are active as long as the tracing guard returned by [`Tracing::init`] is in +/// scope and not dropped. Dropping it results in subscribers being shut down, which can lead to +/// loss of telemetry data when done before exiting the application. This is why it is important +/// to hold onto the guard as long as required. +/// +///
+/// Name the guard variable appropriately, do not just use let _ =, as that will drop +/// immediately. +///
+/// +/// ``` +/// # use stackable_telemetry::tracing::{Tracing, Error}; +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let _tracing_guard = Tracing::builder() // < Scope starts here +/// .service_name("test") // | +/// .build() // | +/// .init()?; // | +/// // | +/// tracing::info!("log a message"); // | +/// Ok(()) // < Scope ends here, guard is dropped +/// } /// ``` -/// use stackable_telemetry::tracing::{Tracing, Error, settings::{Build as _, Settings}}; +/// +/// ## Basic configuration +/// +/// A basic configuration of subscribers can be done by using 2-tuples or 3-tuples, also called +/// doubles and triples. Using tuples, the subscriber can be enabled/disabled and it's environment +/// variable and default level can be set. +/// +/// ``` +/// use stackable_telemetry::tracing::{Tracing, Error, settings::Settings}; /// use tracing_subscriber::filter::LevelFilter; /// /// #[tokio::main] @@ -54,8 +89,36 @@ pub enum Error { /// // runtime. /// let otlp_log_flag = false; /// -/// // IMPORTANT: Name the guard variable appropriately, do not just use -/// // `let _ =`, as that will drop immediately. +/// let _tracing_guard = Tracing::builder() +/// .service_name("test") +/// .with_console_output(("TEST_CONSOLE", LevelFilter::INFO)) +/// .with_otlp_log_exporter(("TEST_OTLP_LOG", LevelFilter::DEBUG, otlp_log_flag)) +/// .build() +/// .init()?; +/// +/// tracing::info!("log a message"); +/// +/// Ok(()) +/// } +/// ``` +/// +/// ## Advanced configuration +/// +/// More advanced configurations can be done via the [`Settings::builder`] function. Each +/// subscriber provides specific settings based on a common set of options. These options can be +/// customized via the following methods: +/// +/// - [`SettingsBuilder::console_log_settings_builder`] +/// - [`SettingsBuilder::otlp_log_settings_builder`] +/// - [`SettingsBuilder::otlp_trace_settings_builder`] +/// +/// ``` +/// # use stackable_telemetry::tracing::{Tracing, Error, settings::Settings}; +/// # use tracing_subscriber::filter::LevelFilter; +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let otlp_log_flag = false; +/// /// let _tracing_guard = Tracing::builder() /// .service_name("test") /// .with_console_output( @@ -164,8 +227,10 @@ impl Tracing { /// Initialise the configured tracing subscribers, returning a guard that /// will shutdown the subscribers when dropped. /// - /// IMPORTANT: Name the guard variable appropriately, do not just use - /// `let _ =`, as that will drop immediately. + ///
+ /// Name the guard variable appropriately, do not just use let _ =, as that will drop + /// immediately. + ///
pub fn init(mut self) -> Result { let mut layers: Vec + Sync + Send>> = Vec::new(); @@ -374,11 +439,11 @@ impl TracingBuilder { /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_console_output( self, - console_log_settings: ConsoleLogSettings, + console_log_settings: impl Into, ) -> TracingBuilder { TracingBuilder { service_name: self.service_name, - console_log_settings, + console_log_settings: console_log_settings.into(), otlp_log_settings: self.otlp_log_settings, otlp_trace_settings: self.otlp_trace_settings, _marker: self._marker, @@ -394,12 +459,12 @@ impl TracingBuilder { /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_otlp_log_exporter( self, - otlp_log_settings: OtlpLogSettings, + otlp_log_settings: impl Into, ) -> TracingBuilder { TracingBuilder { service_name: self.service_name, console_log_settings: self.console_log_settings, - otlp_log_settings, + otlp_log_settings: otlp_log_settings.into(), otlp_trace_settings: self.otlp_trace_settings, _marker: self._marker, } @@ -414,13 +479,13 @@ impl TracingBuilder { /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_otlp_trace_exporter( self, - otlp_trace_settings: OtlpTraceSettings, + otlp_trace_settings: impl Into, ) -> TracingBuilder { TracingBuilder { service_name: self.service_name, console_log_settings: self.console_log_settings, otlp_log_settings: self.otlp_log_settings, - otlp_trace_settings, + otlp_trace_settings: otlp_trace_settings.into(), _marker: self._marker, } } @@ -452,7 +517,8 @@ fn env_filter_builder(env_var: &str, default_directive: impl Into) -> #[cfg(test)] mod test { - use settings::{Build as _, Settings}; + use rstest::rstest; + use settings::Settings; use tracing::level_filters::LevelFilter; use super::*; @@ -499,6 +565,48 @@ mod test { assert!(!trace_guard.otlp_trace_settings.enabled); } + #[test] + fn builder_with_console_output_double() { + let trace_guard = Tracing::builder() + .service_name("test") + .with_console_output(("ABC_A", LevelFilter::TRACE)) + .build(); + + assert_eq!( + trace_guard.console_log_settings, + ConsoleLogSettings { + common_settings: Settings { + environment_variable: "ABC_A", + default_level: LevelFilter::TRACE, + enabled: true + }, + log_format: Default::default() + } + ) + } + + #[rstest] + #[case(false)] + #[case(true)] + fn builder_with_console_output_triple(#[case] enabled: bool) { + let trace_guard = Tracing::builder() + .service_name("test") + .with_console_output(("ABC_A", LevelFilter::TRACE, enabled)) + .build(); + + assert_eq!( + trace_guard.console_log_settings, + ConsoleLogSettings { + common_settings: Settings { + environment_variable: "ABC_A", + default_level: LevelFilter::TRACE, + enabled + }, + log_format: Default::default() + } + ) + } + #[test] fn builder_with_all() { let trace_guard = Tracing::builder() diff --git a/crates/stackable-telemetry/src/tracing/settings/console_log.rs b/crates/stackable-telemetry/src/tracing/settings/console_log.rs index 0ba059592..dc26379fd 100644 --- a/crates/stackable-telemetry/src/tracing/settings/console_log.rs +++ b/crates/stackable-telemetry/src/tracing/settings/console_log.rs @@ -2,7 +2,9 @@ use std::ops::Deref; -use super::{Build, Settings, SettingsBuilder}; +use tracing::level_filters::LevelFilter; + +use super::{Settings, SettingsBuilder}; /// Configure specific settings for the Console Log subscriber. #[derive(Debug, Default, PartialEq)] @@ -74,12 +76,36 @@ impl From for ConsoleLogSettingsBuilder { } } -/// This implementation is used to build console log settings from common settings without -/// specifying console log specific settings. -impl Build for SettingsBuilder { - fn build(self) -> ConsoleLogSettings { +impl From for ConsoleLogSettings { + fn from(common_settings: Settings) -> Self { ConsoleLogSettings { - common_settings: self.build(), + common_settings, + ..Default::default() + } + } +} + +impl From<(&'static str, LevelFilter)> for ConsoleLogSettings { + fn from(value: (&'static str, LevelFilter)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: true, + }, + ..Default::default() + } + } +} + +impl From<(&'static str, LevelFilter, bool)> for ConsoleLogSettings { + fn from(value: (&'static str, LevelFilter, bool)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: value.2, + }, ..Default::default() } } diff --git a/crates/stackable-telemetry/src/tracing/settings/mod.rs b/crates/stackable-telemetry/src/tracing/settings/mod.rs index aaf71c6bb..88deb9d73 100644 --- a/crates/stackable-telemetry/src/tracing/settings/mod.rs +++ b/crates/stackable-telemetry/src/tracing/settings/mod.rs @@ -51,22 +51,6 @@ pub struct SettingsBuilder { default_level: LevelFilter, } -/// Finalizer to be implemented on builders. -pub trait Build { - /// Finalize settings. - fn build(self) -> T; -} - -impl Build for SettingsBuilder { - fn build(self) -> Settings { - Settings { - environment_variable: self.environment_variable, - default_level: self.default_level, - enabled: self.enabled, - } - } -} - impl SettingsBuilder { /// Set the environment variable used for overriding the [`Settings::default_level`]. /// @@ -114,6 +98,15 @@ impl SettingsBuilder { pub fn otlp_trace_settings_builder(self) -> OtlpTraceSettingsBuilder { self.into() } + + /// Consumes self and constructs valid [`Settings`]. + pub fn build(self) -> Settings { + Settings { + environment_variable: self.environment_variable, + default_level: self.default_level, + enabled: self.enabled, + } + } } impl Default for SettingsBuilder { diff --git a/crates/stackable-telemetry/src/tracing/settings/otlp_log.rs b/crates/stackable-telemetry/src/tracing/settings/otlp_log.rs index 6015e0564..e885faaaa 100644 --- a/crates/stackable-telemetry/src/tracing/settings/otlp_log.rs +++ b/crates/stackable-telemetry/src/tracing/settings/otlp_log.rs @@ -2,7 +2,9 @@ use std::ops::Deref; -use super::{Build, Settings, SettingsBuilder}; +use tracing::level_filters::LevelFilter; + +use super::{Settings, SettingsBuilder}; #[derive(Debug, Default, PartialEq)] pub struct OtlpLogSettings { @@ -39,13 +41,32 @@ impl From for OtlpLogSettingsBuilder { } } -/// This implementation is used to build OTLP log settings from common settings without -/// specifying OTLP log specific settings. -impl Build for SettingsBuilder { - fn build(self) -> OtlpLogSettings { - OtlpLogSettings { - common_settings: self.build(), - // ..Default::default() +impl From for OtlpLogSettings { + fn from(common_settings: Settings) -> Self { + Self { common_settings } + } +} + +impl From<(&'static str, LevelFilter)> for OtlpLogSettings { + fn from(value: (&'static str, LevelFilter)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: true, + }, + } + } +} + +impl From<(&'static str, LevelFilter, bool)> for OtlpLogSettings { + fn from(value: (&'static str, LevelFilter, bool)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: value.2, + }, } } } diff --git a/crates/stackable-telemetry/src/tracing/settings/otlp_trace.rs b/crates/stackable-telemetry/src/tracing/settings/otlp_trace.rs index df13a9e2b..98f9ab6fa 100644 --- a/crates/stackable-telemetry/src/tracing/settings/otlp_trace.rs +++ b/crates/stackable-telemetry/src/tracing/settings/otlp_trace.rs @@ -2,7 +2,9 @@ use std::ops::Deref; -use super::{Build, Settings, SettingsBuilder}; +use tracing::level_filters::LevelFilter; + +use super::{Settings, SettingsBuilder}; #[derive(Debug, Default, PartialEq)] pub struct OtlpTraceSettings { @@ -39,13 +41,32 @@ impl From for OtlpTraceSettingsBuilder { } } -/// This implementation is used to build OTLP trace settings from common settings without -/// specifying OTLP trace specific settings. -impl Build for SettingsBuilder { - fn build(self) -> OtlpTraceSettings { - OtlpTraceSettings { - common_settings: self.build(), - // ..Default::default() +impl From for OtlpTraceSettings { + fn from(common_settings: Settings) -> Self { + Self { common_settings } + } +} + +impl From<(&'static str, LevelFilter)> for OtlpTraceSettings { + fn from(value: (&'static str, LevelFilter)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: true, + }, + } + } +} + +impl From<(&'static str, LevelFilter, bool)> for OtlpTraceSettings { + fn from(value: (&'static str, LevelFilter, bool)) -> Self { + Self { + common_settings: Settings { + environment_variable: value.0, + default_level: value.1, + enabled: value.2, + }, } } }