diff --git a/bin/council/src/args.rs b/bin/council/src/args.rs index af71dc6c53..152a2ca5ab 100644 --- a/bin/council/src/args.rs +++ b/bin/council/src/args.rs @@ -19,6 +19,28 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, + /// NATS connection URL [example: demo.nats.io] #[arg(long)] pub(crate) nats_url: Option, @@ -30,10 +52,6 @@ pub(crate) struct Args { /// NATS credentials file #[arg(long)] pub(crate) nats_creds_path: Option, - - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, } impl TryFrom for Config { diff --git a/bin/council/src/main.rs b/bin/council/src/main.rs index 788d92538b..4dc07f0be4 100644 --- a/bin/council/src/main.rs +++ b/bin/council/src/main.rs @@ -25,23 +25,32 @@ async fn async_main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("council") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["council", "council_server"]) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("council") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["council", "council_server"]); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; - let (_shutdown_request_tx, shutdown_request_rx) = watch::channel(()); + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; } trace!(arguments =?args, "parsed cli arguments"); + let (_shutdown_request_tx, shutdown_request_rx) = watch::channel(()); + task_tracker.close(); let config = council_server::server::Config::try_from(args)?; diff --git a/bin/cyclone/src/args.rs b/bin/cyclone/src/args.rs index 177c08e61c..e2fa235736 100644 --- a/bin/cyclone/src/args.rs +++ b/bin/cyclone/src/args.rs @@ -24,9 +24,27 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, /// Binds service to a socket address [example: 0.0.0.0:5157] #[arg(long, group = "bind")] diff --git a/bin/cyclone/src/main.rs b/bin/cyclone/src/main.rs index c604df5344..f211ff501f 100644 --- a/bin/cyclone/src/main.rs +++ b/bin/cyclone/src/main.rs @@ -19,16 +19,25 @@ async fn main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("cyclone") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["cyclone", "cyclone_server"]) - .custom_default_tracing_level(CUSTOM_DEFAULT_TRACING_LEVEL) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("cyclone") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["cyclone", "cyclone_server"]) + .custom_default_tracing_level(CUSTOM_DEFAULT_TRACING_LEVEL); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; + + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; diff --git a/bin/module-index/src/args.rs b/bin/module-index/src/args.rs index 822b266dfd..c6e4a09c84 100644 --- a/bin/module-index/src/args.rs +++ b/bin/module-index/src/args.rs @@ -21,6 +21,28 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, + /// PostgreSQL connection pool dbname [example: myapp] #[arg(long, env)] pub(crate) pg_dbname: Option, @@ -80,14 +102,8 @@ pub(crate) struct Args { /// The path to the JWT public signing key #[arg(long, env)] pub(crate) jwt_public_key: Option, - // /// Database migration mode on startup // #[arg(long, value_parser = PossibleValuesParser::new(MigrationMode::variants()))] - - // pub(crate) migration_mode: Option, - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, } impl TryFrom for Config { diff --git a/bin/module-index/src/main.rs b/bin/module-index/src/main.rs index c3fe37b108..9b3da91e9d 100644 --- a/bin/module-index/src/main.rs +++ b/bin/module-index/src/main.rs @@ -25,15 +25,24 @@ async fn async_main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("module-index") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["module_index", "module_index_server"]) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("module-index") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["module_index", "module_index_server"]); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; + + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; diff --git a/bin/pinga/src/args.rs b/bin/pinga/src/args.rs index 0533368e74..7b10c348ce 100644 --- a/bin/pinga/src/args.rs +++ b/bin/pinga/src/args.rs @@ -21,6 +21,28 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, + /// PostgreSQL connection pool dbname [example: myapp] #[arg(long)] pub(crate) pg_dbname: Option, @@ -61,10 +83,6 @@ pub(crate) struct Args { #[arg(long)] pub(crate) nats_creds_path: Option, - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, - /// Cyclone encryption key file location [default: /run/pinga/cyclone_encryption.key] #[arg(long)] pub(crate) cyclone_encryption_key_path: Option, diff --git a/bin/pinga/src/main.rs b/bin/pinga/src/main.rs index 8940411aee..967a36c752 100644 --- a/bin/pinga/src/main.rs +++ b/bin/pinga/src/main.rs @@ -25,15 +25,24 @@ async fn async_main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("pinga") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["pinga", "pinga_server"]) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("pinga") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["pinga", "pinga_server"]); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; + + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; diff --git a/bin/sdf/src/args.rs b/bin/sdf/src/args.rs index c4361afbe1..db4b1874de 100644 --- a/bin/sdf/src/args.rs +++ b/bin/sdf/src/args.rs @@ -23,6 +23,28 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, + /// PostgreSQL connection pool dbname [example: myapp] #[arg(long)] pub(crate) pg_dbname: Option, @@ -67,10 +89,6 @@ pub(crate) struct Args { #[arg(long, value_parser = PossibleValuesParser::new(MigrationMode::variants()))] pub(crate) migration_mode: Option, - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, - /// Cyclone encryption key file location [default: /run/sdf/cyclone_encryption.key] #[arg(long)] pub(crate) cyclone_encryption_key_path: Option, diff --git a/bin/sdf/src/main.rs b/bin/sdf/src/main.rs index 92e2153be6..7e81d46f3c 100644 --- a/bin/sdf/src/main.rs +++ b/bin/sdf/src/main.rs @@ -36,15 +36,24 @@ async fn async_main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("sdf") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["sdf", "sdf_server"]) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("sdf") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["sdf", "sdf_server"]); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; + + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; diff --git a/bin/veritech/src/args.rs b/bin/veritech/src/args.rs index dac8162240..36d0c2415e 100644 --- a/bin/veritech/src/args.rs +++ b/bin/veritech/src/args.rs @@ -17,6 +17,28 @@ pub(crate) struct Args { #[arg(short = 'v', long = "verbose", action = ArgAction::Count)] pub(crate) verbose: u8, + /// Disables ANSI coloring in log output, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_NO_COLOR", + hide_env_values = true, + conflicts_with = "force_color" + )] + pub(crate) no_color: Option, + + /// Forces ANSI coloring, even if standard output refers to a terminal/TTY. + /// + /// For more details, visit: . + #[arg( + long, + env = "SI_FORCE_COLOR", + hide_env_values = true, + conflicts_with = "no_color" + )] + pub(crate) force_color: Option, + /// NATS connection URL [example: 0.0.0.0:4222] #[arg(long, short = 'u')] pub(crate) nats_url: Option, @@ -29,10 +51,6 @@ pub(crate) struct Args { #[arg(long)] pub(crate) nats_creds_path: Option, - /// Disable OpenTelemetry on startup - #[arg(long)] - pub(crate) disable_opentelemetry: bool, - /// Cyclone runtime type: LocalProcess #[arg(long)] pub(crate) cyclone_local_process: bool, diff --git a/bin/veritech/src/main.rs b/bin/veritech/src/main.rs index e356733feb..afd7ebb99f 100644 --- a/bin/veritech/src/main.rs +++ b/bin/veritech/src/main.rs @@ -11,15 +11,24 @@ async fn main() -> Result<()> { let task_tracker = TaskTracker::new(); color_eyre::install()?; - let config = TelemetryConfig::builder() - .service_name("veritech") - .service_namespace("si") - .log_env_var_prefix("SI") - .app_modules(vec!["veritech", "veritech_server"]) - .build()?; - let (mut telemetry, telemetry_shutdown) = - telemetry_application::init(config, &task_tracker, shutdown_token.clone())?; let args = args::parse(); + let (mut telemetry, telemetry_shutdown) = { + let mut builder = TelemetryConfig::builder(); + builder + .service_name("veritech") + .service_namespace("si") + .log_env_var_prefix("SI") + .app_modules(vec!["veritech", "veritech_server"]); + if let Some(force_color) = args.force_color { + builder.force_color(force_color); + } + if let Some(no_color) = args.no_color { + builder.no_color(no_color); + } + let config = builder.build()?; + + telemetry_application::init(config, &task_tracker, shutdown_token.clone())? + }; if args.verbose > 0 { telemetry.set_verbosity(args.verbose.into()).await?; diff --git a/dev/Tiltfile b/dev/Tiltfile index f6c489b0fd..880fee850a 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -83,6 +83,7 @@ local_resource( labels = ["backend"], cmd = "buck2 build {}".format(module_index_target), serve_cmd = "buck2 run {}".format(module_index_target), + serve_env = {"SI_FORCE_COLOR": "true"}, allow_parallel = True, auto_init = False, resource_deps = [ @@ -100,6 +101,7 @@ local_resource( labels = ["backend"], cmd = "buck2 build {}".format(council_target), serve_cmd = "buck2 run {}".format(council_target), + serve_env = {"SI_FORCE_COLOR": "true"}, allow_parallel = True, resource_deps = [ "nats", @@ -116,6 +118,7 @@ local_resource( labels = ["backend"], cmd = "buck2 build {}".format(pinga_target), serve_cmd = "buck2 run {}".format(pinga_target), + serve_env = {"SI_FORCE_COLOR": "true"}, allow_parallel = True, resource_deps = [ "council", @@ -134,6 +137,7 @@ local_resource( labels = ["backend"], cmd = "buck2 build {}".format(veritech_target), serve_cmd = "SI_LOG=debug buck2 run {}".format(veritech_target), + serve_env = {"SI_FORCE_COLOR": "true"}, # This is the serve command you might need if you want to execute on firecracker for 10 functione executions. # NB: BUCK2 MUST RUN AS ROOT OR THIS WILL NOT WORK # serve_cmd = "SI_LOG=debug buck2 run {} -- --cyclone-local-firecracker --cyclone-pool-size 10".format(veritech_target), @@ -153,6 +157,7 @@ local_resource( labels = ["backend"], cmd = "buck2 build {}".format(sdf_target), serve_cmd = "buck2 run {}".format(sdf_target), + serve_env = {"SI_FORCE_COLOR": "true"}, allow_parallel = True, resource_deps = [ "nats", diff --git a/lib/telemetry-application-rs/src/lib.rs b/lib/telemetry-application-rs/src/lib.rs index 3e9fdb0dfa..07da44e04d 100644 --- a/lib/telemetry-application-rs/src/lib.rs +++ b/lib/telemetry-application-rs/src/lib.rs @@ -10,7 +10,8 @@ use std::{ borrow::Cow, - env, io, + env, + io::{self, IsTerminal}, ops::Deref, thread, time::{Duration, Instant}, @@ -121,6 +122,12 @@ pub struct TelemetryConfig { )] secondary_log_span_events_env_var: Option, + #[builder(setter(into, strip_option), default = "self.default_no_color()")] + no_color: bool, + + #[builder(setter(into, strip_option), default = "false")] + force_color: bool, + #[builder(default = "true")] signal_handlers: bool, } @@ -194,6 +201,17 @@ impl TelemetryConfigBuilder { Some(None) | None => None, } } + + fn default_no_color(&self) -> bool { + // Checks a known/standard var as a fallback. Code upstack will check for an `SI_*` + // prefixed version which should have a higher precendence. + // + // See: + #[allow(clippy::disallowed_methods)] // See rationale in comment above + std::env::var_os("NO_COLOR") + .map(|value| !value.is_empty()) + .unwrap_or(false) + } } pub fn init( @@ -292,6 +310,7 @@ fn tracing_subscriber( let (console_log_layer, console_log_filter_reload) = { let layer = tracing_subscriber::fmt::layer() .with_thread_ids(true) + .with_ansi(should_add_ansi(config)) .with_span_events(span_events_fmt); let env_filter = EnvFilter::try_new(directives.as_str())?; @@ -387,6 +406,18 @@ fn create_client( Ok((client, guard)) } +fn should_add_ansi(config: &TelemetryConfig) -> bool { + if config.force_color { + // If we're forcing colors, then this is unconditionally true + true + } else { + // Otherwise 2 conditions must be met: + // 1. did we *not* ask for `no_color` (or: is `no_color` unset) + // 2. is the standard output file descriptor refer to a terminal or TTY + !config.no_color && io::stdout().is_terminal() + } +} + #[must_use] pub struct TelemetryShutdownGuard { update_telemetry_tx: mpsc::UnboundedSender,