diff --git a/config/development.toml b/config/development.toml index cdbb0188..af5bf100 100644 --- a/config/development.toml +++ b/config/development.toml @@ -1,3 +1,6 @@ +[tracing] +format = "pretty" + [auth.jwt] secret = "secret-dev" diff --git a/config/test.toml b/config/test.toml index 491b3cca..c50d2442 100644 --- a/config/test.toml +++ b/config/test.toml @@ -1,3 +1,6 @@ +[tracing] +format = "pretty" + [auth.jwt] secret = "secret-test" diff --git a/examples/full/config/development.toml b/examples/full/config/development.toml index 2b3082cd..f77e831d 100644 --- a/examples/full/config/development.toml +++ b/examples/full/config/development.toml @@ -1,3 +1,6 @@ +[tracing] +format = "pretty" + [auth.jwt] secret = "secret-dev" diff --git a/examples/full/config/test.toml b/examples/full/config/test.toml index 8780824a..db02878b 100644 --- a/examples/full/config/test.toml +++ b/examples/full/config/test.toml @@ -1,3 +1,6 @@ +[tracing] +format = "pretty" + [auth.jwt] secret = "secret-test" diff --git a/src/config/snapshots/roadster__config__app_config__tests__test.snap b/src/config/snapshots/roadster__config__app_config__tests__test.snap index 57ae0661..83032b06 100644 --- a/src/config/snapshots/roadster__config__app_config__tests__test.snap +++ b/src/config/snapshots/roadster__config__app_config__tests__test.snap @@ -137,6 +137,7 @@ required-claims = [] [tracing] level = 'debug' +format = 'compact' trace-propagation = true [database] diff --git a/src/config/tracing/default.toml b/src/config/tracing/default.toml index 11b05262..72da4c11 100644 --- a/src/config/tracing/default.toml +++ b/src/config/tracing/default.toml @@ -1,2 +1,3 @@ [tracing] +format = "compact" trace-propagation = true diff --git a/src/config/tracing/mod.rs b/src/config/tracing/mod.rs index 0d86ee58..04aab802 100644 --- a/src/config/tracing/mod.rs +++ b/src/config/tracing/mod.rs @@ -1,7 +1,9 @@ #[cfg(feature = "otel")] use crate::util::serde_util::default_true; +use clap::ValueEnum; use config::{FileFormat, FileSourceString}; use serde_derive::{Deserialize, Serialize}; +use strum_macros::{EnumString, IntoStaticStr}; #[cfg(feature = "otel")] use url::Url; use validator::Validate; @@ -16,6 +18,9 @@ pub fn default_config() -> config::File { pub struct Tracing { pub level: String, + /// The format to use when printing traces to logs. + pub format: Format, + /// The name of the service to use for the OpenTelemetry `service.name` field. If not provided, /// will use the [`App::name`][crate::config::app_config::App] config value, translated to `snake_case`. #[cfg(feature = "otel")] @@ -31,6 +36,18 @@ pub struct Tracing { pub otlp_endpoint: Option, } +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, EnumString, IntoStaticStr)] +#[cfg_attr(feature = "cli", derive(ValueEnum))] +#[serde(rename_all = "kebab-case")] +#[strum(serialize_all = "kebab-case")] +#[non_exhaustive] +pub enum Format { + None, + Pretty, + Compact, + Json, +} + // To simplify testing, these are only run when all of the config fields are available #[cfg(all(test, feature = "otel"))] mod deserialize_tests { @@ -49,23 +66,27 @@ mod deserialize_tests { #[case( r#" level = "debug" + format = "compact" "# )] #[case( r#" level = "info" + format = "json" service-name = "foo" "# )] #[case( r#" level = "error" + format = "pretty" trace-propagation = false "# )] #[case( r#" level = "debug" + format = "none" otlp-endpoint = "https://example.com:1234" "# )] diff --git a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_1.snap b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_1.snap similarity index 56% rename from src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_1.snap rename to src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_1.snap index 8e6e78ef..da7f6b23 100644 --- a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_1.snap +++ b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_1.snap @@ -1,6 +1,7 @@ --- -source: src/config/tracing.rs +source: src/config/tracing/mod.rs expression: tracing --- level = 'debug' +format = 'compact' trace-propagation = true diff --git a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_2.snap b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_2.snap similarity index 64% rename from src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_2.snap rename to src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_2.snap index b32f8333..bbf6304d 100644 --- a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_2.snap +++ b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_2.snap @@ -1,7 +1,8 @@ --- -source: src/config/tracing.rs +source: src/config/tracing/mod.rs expression: tracing --- level = 'info' +format = 'json' service-name = 'foo' trace-propagation = true diff --git a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_3.snap b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_3.snap similarity index 57% rename from src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_3.snap rename to src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_3.snap index 4f39fd2c..4c280531 100644 --- a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_3.snap +++ b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_3.snap @@ -1,6 +1,7 @@ --- -source: src/config/tracing.rs +source: src/config/tracing/mod.rs expression: tracing --- level = 'error' +format = 'pretty' trace-propagation = false diff --git a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_4.snap b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_4.snap similarity index 69% rename from src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_4.snap rename to src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_4.snap index eb4791bd..67c6683a 100644 --- a/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__sidekiq@case_4.snap +++ b/src/config/tracing/snapshots/roadster__config__tracing__deserialize_tests__tracing@case_4.snap @@ -1,7 +1,8 @@ --- -source: src/config/tracing.rs +source: src/config/tracing/mod.rs expression: tracing --- level = 'debug' +format = 'none' trace-propagation = true otlp-endpoint = 'https://example.com:1234/' diff --git a/src/tracing/mod.rs b/src/tracing/mod.rs index ace8760c..4d4df190 100644 --- a/src/tracing/mod.rs +++ b/src/tracing/mod.rs @@ -21,6 +21,7 @@ use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; use crate::config::app_config::AppConfig; +use crate::config::tracing::Format; use crate::error::RoadsterResult; // Todo: make this configurable @@ -30,7 +31,42 @@ pub fn init_tracing( metadata: &AppMetadata, ) -> RoadsterResult<()> { // Stdout Layer - let stdout_layer = tracing_subscriber::fmt::layer(); + // Each format results in a different type, so we can't use a `match` on the format enum. + // Instead, we need to create an optional layer of each type, and add all of them to the + // registry -- if a layer is `None`, it won't actually be added. + let compact_log_layer = if matches!(config.tracing.format, Format::Compact) { + Some(tracing_subscriber::fmt::layer().compact()) + } else { + None + }; + let pretty_log_layer = if matches!(config.tracing.format, Format::Pretty) { + Some(tracing_subscriber::fmt::layer().pretty()) + } else { + None + }; + let json_log_layer = if matches!(config.tracing.format, Format::Json) { + Some(tracing_subscriber::fmt::layer().json()) + } else { + None + }; + match config.tracing.format { + Format::None => { + assert!( + pretty_log_layer.is_none() + && compact_log_layer.is_none() + && json_log_layer.is_none() + ) + } + Format::Pretty => { + assert!(pretty_log_layer.is_some()) + } + Format::Compact => { + assert!(compact_log_layer.is_some()) + } + Format::Json => { + assert!(json_log_layer.is_some()) + } + } #[cfg(feature = "otel")] if config.tracing.trace_propagation { @@ -104,7 +140,9 @@ pub fn init_tracing( let registry = tracing_subscriber::Registry::default() .with(env_filter) - .with(stdout_layer); + .with(compact_log_layer) + .with(pretty_log_layer) + .with(json_log_layer); #[cfg(feature = "otel")] let registry = { registry.with(oltp_traces_layer).with(otlp_metrics_layer) };