From 5f8be055db1b276d2bb2d986ac0780997ddc68c4 Mon Sep 17 00:00:00 2001 From: snek Date: Thu, 12 Dec 2024 09:17:26 +0100 Subject: [PATCH] refactor(unstable): otel configuration (#27333) split up otel config into user configurable and runtime configurable parts. user configurable part is now set via env vars parsed according to the otel spec. otel is now enabled via `OTEL_DENO=true`, and `--unstable-otel` only acts as a guard. Fixes: https://github.com/denoland/deno/issues/27273 --- cli/args/flags.rs | 47 +++++++++++++++++++-------- cli/args/mod.rs | 10 +++++- cli/main.rs | 10 +++--- cli/mainrt.rs | 11 ++++--- cli/standalone/binary.rs | 2 +- cli/util/logger.rs | 51 +++++++++++++++++++++++------- cli/worker.rs | 4 +-- ext/telemetry/lib.rs | 32 ++++++++++++------- ext/telemetry/telemetry.ts | 10 +++--- runtime/worker_bootstrap.rs | 11 ++----- tests/specs/cli/otel_basic/main.ts | 1 + 11 files changed, 124 insertions(+), 65 deletions(-) diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 9418739564..418edcf34b 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -37,6 +37,7 @@ use deno_path_util::url_to_file_path; use deno_runtime::deno_permissions::PermissionsOptions; use deno_runtime::deno_permissions::SysDescriptor; use deno_telemetry::OtelConfig; +use deno_telemetry::OtelConsoleConfig; use log::debug; use log::Level; use serde::Deserialize; @@ -986,21 +987,41 @@ impl Flags { args } - pub fn otel_config(&self) -> Option { - if self + pub fn otel_config(&self) -> OtelConfig { + let has_unstable_flag = self .unstable_config .features - .contains(&String::from("otel")) - { - Some(OtelConfig { - runtime_name: Cow::Borrowed("deno"), - runtime_version: Cow::Borrowed(crate::version::DENO_VERSION_INFO.deno), - deterministic: std::env::var("DENO_UNSTABLE_OTEL_DETERMINISTIC") - .is_ok(), - ..Default::default() - }) - } else { - None + .contains(&String::from("otel")); + + let otel_var = |name| match std::env::var(name) { + Ok(s) if s.to_lowercase() == "true" => Some(true), + Ok(s) if s.to_lowercase() == "false" => Some(false), + _ => None, + }; + + let disabled = + !has_unstable_flag || otel_var("OTEL_SDK_DISABLED").unwrap_or(false); + let default = !disabled && otel_var("OTEL_DENO").unwrap_or(false); + + OtelConfig { + tracing_enabled: !disabled + && otel_var("OTEL_DENO_TRACING").unwrap_or(default), + console: match std::env::var("OTEL_DENO_CONSOLE").as_deref() { + Ok(_) if disabled => OtelConsoleConfig::Ignore, + Ok("ignore") => OtelConsoleConfig::Ignore, + Ok("capture") => OtelConsoleConfig::Capture, + Ok("replace") => OtelConsoleConfig::Replace, + _ => { + if default { + OtelConsoleConfig::Capture + } else { + OtelConsoleConfig::Ignore + } + } + }, + deterministic: std::env::var("DENO_UNSTABLE_OTEL_DETERMINISTIC") + .as_deref() + == Ok("1"), } } diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 314c0ff17a..71f79e12e0 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -31,6 +31,7 @@ use deno_npm_cache::NpmCacheSetting; use deno_path_util::normalize_path; use deno_semver::npm::NpmPackageReqReference; use deno_telemetry::OtelConfig; +use deno_telemetry::OtelRuntimeConfig; use import_map::resolve_import_map_value_from_specifier; pub use deno_config::deno_json::BenchConfig; @@ -1130,7 +1131,7 @@ impl CliOptions { } } - pub fn otel_config(&self) -> Option { + pub fn otel_config(&self) -> OtelConfig { self.flags.otel_config() } @@ -2000,6 +2001,13 @@ pub enum NpmCachingStrategy { Manual, } +pub(crate) fn otel_runtime_config() -> OtelRuntimeConfig { + OtelRuntimeConfig { + runtime_name: Cow::Borrowed("deno"), + runtime_version: Cow::Borrowed(crate::version::DENO_VERSION_INFO.deno), + } +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq; diff --git a/cli/main.rs b/cli/main.rs index d47f1e363c..0594739fd8 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -437,20 +437,18 @@ fn resolve_flags_and_init( if err.kind() == clap::error::ErrorKind::DisplayVersion => { // Ignore results to avoid BrokenPipe errors. - util::logger::init(None); + util::logger::init(None, None); let _ = err.print(); deno_runtime::exit(0); } Err(err) => { - util::logger::init(None); + util::logger::init(None, None); exit_for_error(AnyError::from(err)) } }; - if let Some(otel_config) = flags.otel_config() { - deno_telemetry::init(otel_config)?; - } - util::logger::init(flags.log_level); + deno_telemetry::init(crate::args::otel_runtime_config())?; + util::logger::init(flags.log_level, Some(flags.otel_config())); // TODO(bartlomieju): remove in Deno v2.5 and hard error then. if flags.unstable_config.legacy_flag_enabled { diff --git a/cli/mainrt.rs b/cli/mainrt.rs index 7ad3b3744b..18142bd0e7 100644 --- a/cli/mainrt.rs +++ b/cli/mainrt.rs @@ -87,17 +87,18 @@ fn main() { let future = async move { match standalone { Ok(Some(data)) => { - if let Some(otel_config) = data.metadata.otel_config.clone() { - deno_telemetry::init(otel_config)?; - } - util::logger::init(data.metadata.log_level); + deno_telemetry::init(crate::args::otel_runtime_config())?; + util::logger::init( + data.metadata.log_level, + Some(data.metadata.otel_config.clone()), + ); load_env_vars(&data.metadata.env_vars_from_env_file); let exit_code = standalone::run(data).await?; deno_runtime::exit(exit_code); } Ok(None) => Ok(()), Err(err) => { - util::logger::init(None); + util::logger::init(None, None); Err(err) } } diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 7728728a26..1e7b0d3f70 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -192,7 +192,7 @@ pub struct Metadata { pub entrypoint_key: String, pub node_modules: Option, pub unstable_config: UnstableConfig, - pub otel_config: Option, // None means disabled. + pub otel_config: OtelConfig, } fn write_binary_bytes( diff --git a/cli/util/logger.rs b/cli/util/logger.rs index 2b8987c3e7..783f8a5f68 100644 --- a/cli/util/logger.rs +++ b/cli/util/logger.rs @@ -1,24 +1,34 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::draw_thread::DrawThread; +use deno_telemetry::OtelConfig; +use deno_telemetry::OtelConsoleConfig; use std::io::Write; -use super::draw_thread::DrawThread; - -struct CliLogger(env_logger::Logger); +struct CliLogger { + otel_console_config: OtelConsoleConfig, + logger: env_logger::Logger, +} impl CliLogger { - pub fn new(logger: env_logger::Logger) -> Self { - Self(logger) + pub fn new( + logger: env_logger::Logger, + otel_console_config: OtelConsoleConfig, + ) -> Self { + Self { + logger, + otel_console_config, + } } pub fn filter(&self) -> log::LevelFilter { - self.0.filter() + self.logger.filter() } } impl log::Log for CliLogger { fn enabled(&self, metadata: &log::Metadata) -> bool { - self.0.enabled(metadata) + self.logger.enabled(metadata) } fn log(&self, record: &log::Record) { @@ -28,18 +38,30 @@ impl log::Log for CliLogger { // could potentially block other threads that access the draw // thread's state DrawThread::hide(); - self.0.log(record); - deno_telemetry::handle_log(record); + + match self.otel_console_config { + OtelConsoleConfig::Ignore => { + self.logger.log(record); + } + OtelConsoleConfig::Capture => { + self.logger.log(record); + deno_telemetry::handle_log(record); + } + OtelConsoleConfig::Replace => { + deno_telemetry::handle_log(record); + } + } + DrawThread::show(); } } fn flush(&self) { - self.0.flush(); + self.logger.flush(); } } -pub fn init(maybe_level: Option) { +pub fn init(maybe_level: Option, otel_config: Option) { let log_level = maybe_level.unwrap_or(log::Level::Info); let logger = env_logger::Builder::from_env( env_logger::Env::new() @@ -93,7 +115,12 @@ pub fn init(maybe_level: Option) { }) .build(); - let cli_logger = CliLogger::new(logger); + let cli_logger = CliLogger::new( + logger, + otel_config + .map(|c| c.console) + .unwrap_or(OtelConsoleConfig::Ignore), + ); let max_level = cli_logger.filter(); let r = log::set_boxed_logger(Box::new(cli_logger)); if r.is_ok() { diff --git a/cli/worker.rs b/cli/worker.rs index 81b8cd2f83..0bbc27b29f 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -154,7 +154,7 @@ struct SharedWorkerState { storage_key_resolver: StorageKeyResolver, options: CliMainWorkerOptions, subcommand: DenoSubcommand, - otel_config: Option, // `None` means OpenTelemetry is disabled. + otel_config: OtelConfig, default_npm_caching_strategy: NpmCachingStrategy, } @@ -426,7 +426,7 @@ impl CliMainWorkerFactory { storage_key_resolver: StorageKeyResolver, subcommand: DenoSubcommand, options: CliMainWorkerOptions, - otel_config: Option, + otel_config: OtelConfig, default_npm_caching_strategy: NpmCachingStrategy, ) -> Self { Self { diff --git a/ext/telemetry/lib.rs b/ext/telemetry/lib.rs index 06210a70e7..816e838743 100644 --- a/ext/telemetry/lib.rs +++ b/ext/telemetry/lib.rs @@ -97,13 +97,28 @@ deno_core::extension!( ); #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OtelConfig { +pub struct OtelRuntimeConfig { pub runtime_name: Cow<'static, str>, pub runtime_version: Cow<'static, str>, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct OtelConfig { + pub tracing_enabled: bool, pub console: OtelConsoleConfig, pub deterministic: bool, } +impl OtelConfig { + pub fn as_v8(&self) -> Box<[u8]> { + Box::new([ + self.tracing_enabled as u8, + self.console as u8, + self.deterministic as u8, + ]) + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[repr(u8)] pub enum OtelConsoleConfig { @@ -112,14 +127,9 @@ pub enum OtelConsoleConfig { Replace = 2, } -impl Default for OtelConfig { +impl Default for OtelConsoleConfig { fn default() -> Self { - Self { - runtime_name: Cow::Borrowed(env!("CARGO_PKG_NAME")), - runtime_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")), - console: OtelConsoleConfig::Capture, - deterministic: false, - } + Self::Ignore } } @@ -411,16 +421,14 @@ static BUILT_IN_INSTRUMENTATION_SCOPE: OnceCell< opentelemetry::InstrumentationScope, > = OnceCell::new(); -pub fn init(config: OtelConfig) -> anyhow::Result<()> { +pub fn init(config: OtelRuntimeConfig) -> anyhow::Result<()> { // Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_* // crates don't do this automatically. // TODO(piscisaureus): enable GRPC support. let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() { Ok("http/protobuf") => Protocol::HttpBinary, Ok("http/json") => Protocol::HttpJson, - Ok("") | Err(env::VarError::NotPresent) => { - return Ok(()); - } + Ok("") | Err(env::VarError::NotPresent) => Protocol::HttpBinary, Ok(protocol) => { return Err(anyhow!( "Env var OTEL_EXPORTER_OTLP_PROTOCOL specifies an unsupported protocol: {}", diff --git a/ext/telemetry/telemetry.ts b/ext/telemetry/telemetry.ts index acdfd4d715..d1335f65b5 100644 --- a/ext/telemetry/telemetry.ts +++ b/ext/telemetry/telemetry.ts @@ -950,15 +950,15 @@ const otelConsoleConfig = { }; export function bootstrap( - config: [] | [ + config: [ + 0 | 1, typeof otelConsoleConfig[keyof typeof otelConsoleConfig], - number, + 0 | 1, ], ): void { - if (config.length === 0) return; - const { 0: consoleConfig, 1: deterministic } = config; + const { 0: tracingEnabled, 1: consoleConfig, 2: deterministic } = config; - TRACING_ENABLED = true; + TRACING_ENABLED = tracingEnabled === 1; DETERMINISTIC = deterministic === 1; switch (consoleConfig) { diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs index 4a8c5dba86..2020c2bc8d 100644 --- a/runtime/worker_bootstrap.rs +++ b/runtime/worker_bootstrap.rs @@ -119,8 +119,7 @@ pub struct BootstrapOptions { // Used by `deno serve` pub serve_port: Option, pub serve_host: Option, - // OpenTelemetry output options. If `None`, OpenTelemetry is disabled. - pub otel_config: Option, + pub otel_config: OtelConfig, } impl Default for BootstrapOptions { @@ -155,7 +154,7 @@ impl Default for BootstrapOptions { mode: WorkerExecutionMode::None, serve_port: Default::default(), serve_host: Default::default(), - otel_config: None, + otel_config: Default::default(), } } } @@ -225,11 +224,7 @@ impl BootstrapOptions { self.serve_host.as_deref(), serve_is_main, serve_worker_count, - if let Some(otel_config) = self.otel_config.as_ref() { - Box::new([otel_config.console as u8, otel_config.deterministic as u8]) - } else { - Box::new([]) - }, + self.otel_config.as_v8(), ); bootstrap.serialize(ser).unwrap() diff --git a/tests/specs/cli/otel_basic/main.ts b/tests/specs/cli/otel_basic/main.ts index ccba126cc1..634727cea7 100644 --- a/tests/specs/cli/otel_basic/main.ts +++ b/tests/specs/cli/otel_basic/main.ts @@ -13,6 +13,7 @@ const server = Deno.serve( const command = new Deno.Command(Deno.execPath(), { args: ["run", "-A", "-q", "--unstable-otel", Deno.args[0]], env: { + OTEL_DENO: "true", DENO_UNSTABLE_OTEL_DETERMINISTIC: "1", OTEL_EXPORTER_OTLP_PROTOCOL: "http/json", OTEL_EXPORTER_OTLP_ENDPOINT: `http://localhost:${port}`,