diff --git a/Cargo.lock b/Cargo.lock index 406a0bdd..ef13db4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3209,6 +3209,7 @@ dependencies = [ "snafu 0.8.5", "stackable-operator-derive", "stackable-shared", + "stackable-telemetry", "strum", "tempfile", "time", @@ -3247,6 +3248,7 @@ name = "stackable-telemetry" version = "0.4.0" dependencies = [ "axum 0.8.3", + "clap", "futures-util", "opentelemetry", "opentelemetry-appender-tracing", @@ -3256,6 +3258,7 @@ dependencies = [ "rstest", "snafu 0.8.5", "stackable-webhook", + "strum", "tokio", "tower 0.5.2", "tracing", diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 06534571..37096f4f 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- BREAKING: Remove `cli::TelemetryArguments` and `cli::RollingPeriod` which are both replaced by + types from `stackable_telemetry` ([#1001]). +- BREAKING: The `ProductOperatorRun` struct now uses `stackable_telemetry::tracing::TelemetryOptions` + for the `telemetry_arguments` field ([#1001]). + +[#1001]: https://github.com/stackabletech/operator-rs/pull/1001 + ## [0.90.0] - 2025-04-07 ### Added diff --git a/crates/stackable-operator/Cargo.toml b/crates/stackable-operator/Cargo.toml index 14b4a516..cff1069e 100644 --- a/crates/stackable-operator/Cargo.toml +++ b/crates/stackable-operator/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true time = ["dep:time"] [dependencies] +stackable-telemetry = { path = "../stackable-telemetry", features = ["clap"]} stackable-operator-derive = { path = "../stackable-operator-derive" } stackable-shared = { path = "../stackable-shared" } diff --git a/crates/stackable-operator/src/cli.rs b/crates/stackable-operator/src/cli.rs index a415e9a1..f3a505eb 100644 --- a/crates/stackable-operator/src/cli.rs +++ b/crates/stackable-operator/src/cli.rs @@ -108,13 +108,13 @@ //! use std::{ ffi::OsStr, - ops::Deref, path::{Path, PathBuf}, }; use clap::Args; use product_config::ProductConfigManager; use snafu::{ResultExt, Snafu}; +use stackable_telemetry::tracing::TelemetryOptions; use crate::{namespace::WatchNamespace, utils::cluster_info::KubernetesClusterInfoOpts}; @@ -165,10 +165,8 @@ pub enum Command { /// ```rust /// # use stackable_operator::cli::{Command, ProductOperatorRun, ProductConfigPath}; /// use clap::Parser; -/// use stackable_operator::{ -/// cli::TelemetryArguments, -/// namespace::WatchNamespace, -/// }; +/// use stackable_operator::namespace::WatchNamespace; +/// use stackable_telemetry::tracing::TelemetryOptions; /// /// #[derive(clap::Parser, Debug, PartialEq, Eq)] /// struct Run { @@ -184,7 +182,7 @@ pub enum Command { /// common: ProductOperatorRun { /// product_config: ProductConfigPath::from("bar".as_ref()), /// watch_namespace: WatchNamespace::One("foobar".to_string()), -/// telemetry_arguments: TelemetryArguments::default(), +/// telemetry_arguments: TelemetryOptions::default(), /// cluster_info_opts: Default::default(), /// }, /// })); @@ -219,7 +217,7 @@ pub struct ProductOperatorRun { pub watch_namespace: WatchNamespace, #[command(flatten)] - pub telemetry_arguments: TelemetryArguments, + pub telemetry_arguments: TelemetryOptions, #[command(flatten)] pub cluster_info_opts: KubernetesClusterInfoOpts, @@ -280,50 +278,6 @@ impl ProductConfigPath { } } -#[derive(Debug, Default, PartialEq, Eq, Args)] -pub struct TelemetryArguments { - /// Disable console output. - #[arg(long, env)] - pub no_console_output: bool, - - /// Enable logging to rolling files located in the specified DIRECTORY. - #[arg(long, env, value_name = "DIRECTORY", group = "rolling_logs_group")] - pub rolling_logs: Option, - - /// Time PERIOD after which log files are rolled over. - #[arg(long, env, value_name = "PERIOD", requires = "rolling_logs_group")] - pub rolling_logs_period: Option, - - /// Enable exporting traces via OTLP. - #[arg(long, env)] - pub otlp_traces: bool, - - /// Enable exporting logs via OTLP. - #[arg(long, env)] - pub otlp_logs: bool, -} - -#[derive(Clone, Debug, PartialEq, Eq, strum::Display, strum::EnumString, clap::ValueEnum)] -pub enum RollingPeriod { - Minutely, - Hourly, - Daily, - Never, -} - -impl Deref for RollingPeriod { - type Target = tracing_appender::rolling::Rotation; - - fn deref(&self) -> &Self::Target { - match self { - RollingPeriod::Minutely => &tracing_appender::rolling::Rotation::MINUTELY, - RollingPeriod::Hourly => &tracing_appender::rolling::Rotation::HOURLY, - RollingPeriod::Daily => &tracing_appender::rolling::Rotation::DAILY, - RollingPeriod::Never => &tracing_appender::rolling::Rotation::NEVER, - } - } -} - #[cfg(test)] mod tests { use std::{env, fs::File}; diff --git a/crates/stackable-telemetry/CHANGELOG.md b/crates/stackable-telemetry/CHANGELOG.md index 2df9840d..d4647128 100644 --- a/crates/stackable-telemetry/CHANGELOG.md +++ b/crates/stackable-telemetry/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add new `Tracing::pre_configured` method ([#1001]). + - Add `TelemetryOptions` struct and `RollingPeriod` enum + - Add `clap` feature to enable `TelemetryOptions` being used as CLI arguments + +### Changed + +- BREAKING: Change `FileLogSettingsBuilder::with_rotation_period` to take `impl Into` + instead of `Rotation` ([#1001]). + +[#1001]: https://github.com/stackabletech/operator-rs/pull/1001 + ## [0.4.0] - 2025-04-02 ### Added diff --git a/crates/stackable-telemetry/Cargo.toml b/crates/stackable-telemetry/Cargo.toml index 1c75fb9f..dc889a01 100644 --- a/crates/stackable-telemetry/Cargo.toml +++ b/crates/stackable-telemetry/Cargo.toml @@ -6,8 +6,12 @@ license.workspace = true edition.workspace = true repository.workspace = true +[features] +clap = ["dep:clap"] + [dependencies] axum.workspace = true +clap = { workspace = true, optional = true } futures-util.workspace = true opentelemetry = { workspace = true, features = ["logs"] } opentelemetry-appender-tracing.workspace = true @@ -16,6 +20,7 @@ opentelemetry-otlp = { workspace = true, features = ["grpc-tonic", "gzip-tonic", opentelemetry_sdk = { workspace = true, features = ["logs", "rt-tokio", "spec_unstable_logs_enabled"] } pin-project.workspace = true snafu.workspace = true +strum.workspace = true tokio.workspace = true tower.workspace = true tracing.workspace = true diff --git a/crates/stackable-telemetry/src/tracing/mod.rs b/crates/stackable-telemetry/src/tracing/mod.rs index ce2e60c5..19bd7e57 100644 --- a/crates/stackable-telemetry/src/tracing/mod.rs +++ b/crates/stackable-telemetry/src/tracing/mod.rs @@ -6,6 +6,10 @@ //! //! To get started, see [`Tracing`]. +use std::path::PathBuf; + +#[cfg_attr(feature = "clap", cfg(doc))] +use clap; use opentelemetry::trace::TracerProvider; use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use opentelemetry_otlp::{LogExporter, SpanExporter}; @@ -14,7 +18,7 @@ use opentelemetry_sdk::{ trace::SdkTracerProvider, }; use snafu::{ResultExt as _, Snafu}; -use tracing::subscriber::SetGlobalDefaultError; +use tracing::{level_filters::LevelFilter, subscriber::SetGlobalDefaultError}; use tracing_appender::rolling::{InitError, RollingFileAppender}; use tracing_subscriber::{EnvFilter, Layer, Registry, filter::Directive, layer::SubscriberExt}; @@ -60,13 +64,11 @@ pub enum Error { /// /// # 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. +/// ## Tracing Guard /// -/// 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 +/// The configured 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. /// ///
@@ -88,7 +90,46 @@ pub enum Error { /// } /// ``` /// -/// ## Basic configuration +/// ## Pre-configured Tracing Instance +/// +/// There are two different styles to configure a [`Tracing`] instance: Using an opinionated pre- +/// configured instance or a fully customizable builder. The first option should be suited for +/// pretty much all operators by using sane defaults and applying best practices out-of-the-box. +/// [`Tracing::pre_configured`] lists details about environment variables, filter levels and +/// defaults used. +/// +/// ``` +/// use stackable_telemetry::tracing::{Tracing, TelemetryOptions, Error}; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let options = TelemetryOptions { +/// no_console_output: false, +/// rolling_logs: None, +/// rolling_logs_period: None, +/// otlp_traces: true, +/// otlp_logs: true, +/// }; +/// +/// let _tracing_guard = Tracing::pre_configured("test", options).init()?; +/// +/// tracing::info!("log a message"); +/// +/// Ok(()) +/// } +/// ``` +/// +/// Also see the documentation for [`TelemetryOptions`] which details how it can be used as CLI +/// arguments via [`clap`]. +/// +/// ## Builders +/// +/// When choosing the builder, 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. +/// +/// ### 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 @@ -118,7 +159,7 @@ pub enum Error { /// } /// ``` /// -/// ## Advanced configuration +/// ### 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 @@ -140,26 +181,26 @@ pub enum Error { /// .service_name("test") /// .with_console_output( /// Settings::builder() -/// .with_environment_variable("TEST_CONSOLE") +/// .with_environment_variable("CONSOLE_LOG") /// .with_default_level(LevelFilter::INFO) /// .build() /// ) /// .with_file_output( /// Settings::builder() -/// .with_environment_variable("TEST_FILE_LOG") +/// .with_environment_variable("FILE_LOG") /// .with_default_level(LevelFilter::INFO) -/// .file_log_settings_builder("/tmp/logs", "tracing-rs.log") +/// .file_log_settings_builder("/tmp/logs", "operator.log") /// .build() /// ) /// .with_otlp_log_exporter(otlp_log_flag.then(|| { /// Settings::builder() -/// .with_environment_variable("TEST_OTLP_LOG") +/// .with_environment_variable("OTLP_LOG") /// .with_default_level(LevelFilter::DEBUG) /// .build() /// })) /// .with_otlp_trace_exporter( /// Settings::builder() -/// .with_environment_variable("TEST_OTLP_TRACE") +/// .with_environment_variable("OTLP_TRACE") /// .with_default_level(LevelFilter::TRACE) /// .build() /// ) @@ -244,12 +285,70 @@ pub struct Tracing { } impl Tracing { + /// The environment variable used to set the console log level filter. + pub const CONSOLE_LOG_ENV_VAR: &str = "CONSOLE_LOG"; + /// The environment variable used to set the rolling file log level filter. + pub const FILE_LOG_ENV_VAR: &str = "FILE_LOG"; + /// The filename used for the rolling file logs. + pub const FILE_LOG_SUFFIX: &str = "tracing-rs.json"; + /// The environment variable used to set the OTLP log level filter. + pub const OTLP_LOG_ENV_VAR: &str = "OTLP_LOG"; + /// The environment variable used to set the OTLP trace level filter. + pub const OTLP_TRACE_ENV_VAR: &str = "OTLP_TRACE"; + /// Creates and returns a [`TracingBuilder`]. pub fn builder() -> TracingBuilder { TracingBuilder::default() } - /// Initialise the configured tracing subscribers, returning a guard that + /// Creates an returns a pre-configured [`Tracing`] instance which can be initialized by + /// calling [`Tracing::init()`]. + /// + /// ### Environment Variables and Default Levels + /// + /// | Level Filter for | Environment Variable | Default Level | + /// | ---------------- | ------------------------------------------ | ------------- | + /// | Console logs | [`CONSOLE_LOG`](Self::CONSOLE_LOG_ENV_VAR) | `INFO` | + /// | File logs | [`FILE_LOG`](Self::FILE_LOG_ENV_VAR) | `INFO` | + /// | OTLP logs | [`OTLP_LOG`](Self::OTLP_LOG_ENV_VAR) | `INFO` | + /// | OTLP traces | [`OTLP_TRACE`](Self::OTLP_TRACE_ENV_VAR) | `INFO` | + /// + /// ### Default Values + /// + /// - If `rolling_logs_period` is [`None`], this function will use a default value of + /// [`RollingPeriod::Never`]. + pub fn pre_configured(service_name: &'static str, options: TelemetryOptions) -> Self { + let TelemetryOptions { + no_console_output, + rolling_logs, + rolling_logs_period, + otlp_traces, + otlp_logs, + } = options; + + let rolling_logs_period = rolling_logs_period.unwrap_or_default(); + + Self::builder() + .service_name(service_name) + .with_console_output(( + Self::CONSOLE_LOG_ENV_VAR, + LevelFilter::INFO, + !no_console_output, + )) + .with_file_output(rolling_logs.map(|log_directory| { + Settings::builder() + .with_environment_variable(Self::FILE_LOG_ENV_VAR) + .with_default_level(LevelFilter::INFO) + .file_log_settings_builder(log_directory, Self::FILE_LOG_SUFFIX) + .with_rotation_period(rolling_logs_period) + .build() + })) + .with_otlp_log_exporter((Self::OTLP_LOG_ENV_VAR, LevelFilter::INFO, otlp_logs)) + .with_otlp_trace_exporter((Self::OTLP_TRACE_ENV_VAR, LevelFilter::INFO, otlp_traces)) + .build() + } + + /// Initialize the configured tracing subscribers, returning a guard that /// will shutdown the subscribers when dropped. /// ///
@@ -500,10 +599,8 @@ impl TracingBuilder { impl TracingBuilder { /// Enable the console output tracing subscriber and set the default - /// [`LevelFilter`][1] which is overridable through the given environment + /// [`LevelFilter`] which is overridable through the given environment /// variable. - /// - /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_console_output( self, console_log_settings: impl Into, @@ -519,10 +616,8 @@ impl TracingBuilder { } /// Enable the file output tracing subscriber and set the default - /// [`LevelFilter`][1] which is overridable through the given environment + /// [`LevelFilter`] which is overridable through the given environment /// variable. - /// - /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_file_output( self, file_log_settings: impl Into, @@ -537,13 +632,11 @@ impl TracingBuilder { } } - /// Enable the OTLP logging subscriber and set the default [`LevelFilter`][1] + /// Enable the OTLP logging subscriber and set the default [`LevelFilter`] /// which is overridable through the given environment variable. /// /// You can configure the OTLP log exports through the variables defined /// in the opentelemetry crates. See [`Tracing`]. - /// - /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_otlp_log_exporter( self, otlp_log_settings: impl Into, @@ -558,13 +651,11 @@ impl TracingBuilder { } } - /// Enable the OTLP tracing subscriber and set the default [`LevelFilter`][1] + /// Enable the OTLP tracing subscriber and set the default [`LevelFilter`] /// which is overridable through the given environment variable. /// /// You can configure the OTLP trace exports through the variables defined /// in the opentelemetry crates. See [`Tracing`]. - /// - /// [1]: tracing_subscriber::filter::LevelFilter pub fn with_otlp_trace_exporter( self, otlp_trace_settings: impl Into, @@ -606,6 +697,88 @@ fn env_filter_builder(env_var: &str, default_directive: impl Into) -> .from_env_lossy() } +/// Contains options which can be passed to [`Tracing::pre_configured()`]. +/// +/// Additionally, this struct can be used as operator CLI arguments. This functionality is only +/// available if the feature `clap` is enabled. +/// +#[cfg_attr( + feature = "clap", + doc = r#" +``` +# use stackable_telemetry::tracing::TelemetryOptions; +use clap::Parser; + +#[derive(Parser)] +struct Cli { + #[arg(short, long)] + namespace: String, + + #[clap(flatten)] + telemetry_arguments: TelemetryOptions, +} +``` +"# +)] +#[cfg_attr(feature = "clap", derive(clap::Args, PartialEq, Eq))] +#[derive(Debug, Default)] +pub struct TelemetryOptions { + /// Disable console output. + #[cfg_attr(feature = "clap", arg(long, env))] + pub no_console_output: bool, + + /// Enable logging to rolling files located in the specified DIRECTORY. + #[cfg_attr( + feature = "clap", + arg( + long, + env = "ROLLING_LOGS_DIR", + value_name = "DIRECTORY", + group = "rolling_logs_group" + ) + )] + pub rolling_logs: Option, + + /// Time PERIOD after which log files are rolled over. + #[cfg_attr( + feature = "clap", + arg(long, env, value_name = "PERIOD", requires = "rolling_logs_group") + )] + pub rolling_logs_period: Option, + + /// Enable exporting traces via OTLP. + #[cfg_attr(feature = "clap", arg(long, env))] + pub otlp_traces: bool, + + /// Enable exporting logs via OTLP. + #[cfg_attr(feature = "clap", arg(long, env))] + pub otlp_logs: bool, +} + +/// Supported periods when the log file is rolled over. +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +#[derive(Clone, Debug, Default, PartialEq, Eq, strum::Display, strum::EnumString)] +#[allow(missing_docs)] +pub enum RollingPeriod { + Minutely, + Hourly, + Daily, + + #[default] + Never, +} + +impl From for Rotation { + fn from(value: RollingPeriod) -> Self { + match value { + RollingPeriod::Minutely => Self::MINUTELY, + RollingPeriod::Hourly => Self::HOURLY, + RollingPeriod::Daily => Self::DAILY, + RollingPeriod::Never => Self::NEVER, + } + } +} + #[cfg(test)] mod test { use std::path::PathBuf; @@ -775,7 +948,7 @@ mod test { let enable_otlp_trace = true; let enable_otlp_log = false; - let tracing_builder = Tracing::builder() + let tracing_guard = Tracing::builder() .service_name("test") .with_console_output(enable_console_output.then(|| { Settings::builder() @@ -800,9 +973,22 @@ mod test { })) .build(); - assert!(tracing_builder.console_log_settings.is_enabled()); - assert!(tracing_builder.file_log_settings.is_enabled()); - assert!(tracing_builder.otlp_trace_settings.is_enabled()); - assert!(tracing_builder.otlp_log_settings.is_disabled()); + assert!(tracing_guard.console_log_settings.is_enabled()); + assert!(tracing_guard.file_log_settings.is_enabled()); + assert!(tracing_guard.otlp_trace_settings.is_enabled()); + assert!(tracing_guard.otlp_log_settings.is_disabled()); + } + + #[test] + fn pre_configured() { + let tracing = Tracing::pre_configured("test", TelemetryOptions { + no_console_output: false, + rolling_logs: None, + rolling_logs_period: None, + otlp_traces: true, + otlp_logs: false, + }); + + assert!(tracing.otlp_trace_settings.is_enabled()); } } diff --git a/crates/stackable-telemetry/src/tracing/settings/file_log.rs b/crates/stackable-telemetry/src/tracing/settings/file_log.rs index 60ba9f04..60fb21f8 100644 --- a/crates/stackable-telemetry/src/tracing/settings/file_log.rs +++ b/crates/stackable-telemetry/src/tracing/settings/file_log.rs @@ -57,8 +57,8 @@ pub struct FileLogSettingsBuilder { impl FileLogSettingsBuilder { /// Set file rotation period. - pub fn with_rotation_period(mut self, rotation_period: Rotation) -> Self { - self.rotation_period = rotation_period; + pub fn with_rotation_period(mut self, rotation_period: impl Into) -> Self { + self.rotation_period = rotation_period.into(); self }