From 92c4fe6adb7f65c3aa7aa9bd900e6bf782f39546 Mon Sep 17 00:00:00 2001 From: Maximiliano Duthey Date: Thu, 21 Aug 2025 16:46:50 -0300 Subject: [PATCH 1/3] feat: implement OpenTelemetry for command execution metrics --- Cargo.lock | 144 +++++++++++++++++++-- Cargo.toml | 5 + src/commands/telemetry.rs | 7 + src/global.rs | 22 +++- src/main.rs | 31 ++++- src/telemetry.rs | 260 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 456 insertions(+), 13 deletions(-) create mode 100644 src/telemetry.rs diff --git a/Cargo.lock b/Cargo.lock index a79a610..0dea9d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,7 +227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", - "rand_core", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -1888,6 +1888,78 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf416e4cb72756655126f7dd7bb0af49c674f4c1b9903e80c009e0c37e552e6" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e046fd7660710fe5a05e8748e70d9058dc15c94ba914e7c4faa7c728f0e8ddc" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic 0.13.1", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f644aa9e5e31d11896e024305d7e3c98a88884d9f8919dbf37a9991bc47a4b" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1946,7 +2018,7 @@ dependencies = [ "pallas-crypto", "pallas-primitives", "pallas-traverse", - "rand", + "rand 0.8.5", ] [[package]] @@ -1988,7 +2060,7 @@ dependencies = [ "cryptoxide 0.4.4", "hex", "pallas-codec", - "rand_core", + "rand_core 0.6.4", "serde", "thiserror 1.0.69", "zeroize", @@ -2005,7 +2077,7 @@ dependencies = [ "itertools 0.13.0", "pallas-codec", "pallas-crypto", - "rand", + "rand 0.8.5", "socket2", "thiserror 1.0.69", "tokio", @@ -2089,7 +2161,7 @@ dependencies = [ "cryptoxide 0.4.4", "ed25519-bip32", "pallas-crypto", - "rand", + "rand 0.8.5", "thiserror 1.0.69", ] @@ -2419,8 +2491,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2430,7 +2512,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2442,6 +2534,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "redox_syscall" version = "0.5.13" @@ -2520,6 +2621,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", @@ -3327,6 +3429,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project", + "prost", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -3437,6 +3560,9 @@ dependencies = [ "miette", "oci-client", "octocrab", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", "pallas", "reqwest", "serde", @@ -3584,7 +3710,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tonic", + "tonic 0.12.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9b52127..3c5e0ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,11 @@ bip39 = "2.1.0" octocrab = "0.44" serde_with = "3.14.0" +# OpenTelemetry for metrics (OTLP over HTTP) +opentelemetry = { version = "0.30.0", features = ["metrics"] } +opentelemetry-otlp = { version = "0.30.0", features = ["metrics"] } +opentelemetry_sdk = { version = "0.30.0", features = ["metrics"] } + [features] unstable = [] diff --git a/src/commands/telemetry.rs b/src/commands/telemetry.rs index ff1e250..aef4206 100644 --- a/src/commands/telemetry.rs +++ b/src/commands/telemetry.rs @@ -44,6 +44,13 @@ fn print_status(config: &crate::global::Config) { if config.telemetry.enabled { print_telemetry_info(); println!("Telemetry: ON"); + + // Shows user fingerprint if available + if let Some(ref user_fingerprint) = config.telemetry.user_fingerprint { + println!("User Fingerprint: {}", user_fingerprint); + } else if let Ok(user_fingerprint) = crate::telemetry::get_user_fingerprint() { + println!("User Fingerprint: {}", user_fingerprint); + } return; } diff --git a/src/global.rs b/src/global.rs index 31ede6e..b32a616 100644 --- a/src/global.rs +++ b/src/global.rs @@ -9,10 +9,17 @@ pub struct Config { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct TelemetryConfig { pub enabled: bool, + pub user_fingerprint: Option, + pub otlp_endpoint: Option, } + impl Default for TelemetryConfig { fn default() -> Self { - Self { enabled: true } + Self { + enabled: true, + user_fingerprint: None, + otlp_endpoint: None, + } } } @@ -22,8 +29,19 @@ pub fn ensure_global_config() -> miette::Result<()> { if !trix_path.exists() { std::fs::create_dir_all(trix_path.parent().unwrap()).into_diagnostic()?; - save_config(&Config::default())?; + let mut config = Config::default(); + // Generate user fingerprint when creating config for the first time + config.telemetry.user_fingerprint = Some(crate::telemetry::generate_user_fingerprint()?); + config.telemetry.otlp_endpoint = Some(crate::telemetry::DEFAULT_TELEMETRY_ENDPOINT.to_string()); + save_config(&config)?; print_telemetry_info(); + } else { + // Ensure existing config has a user fingerprint and otlp_endpoint + let mut config = read_config()?; + if config.telemetry.user_fingerprint.is_none() { + config.telemetry.user_fingerprint = Some(crate::telemetry::generate_user_fingerprint()?); + save_config(&config)?; + } } Ok(()) diff --git a/src/main.rs b/src/main.rs index a8b1757..9dc5f93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod dirs; mod global; mod home; mod spawn; +mod telemetry; mod updates; use commands as cmds; @@ -62,6 +63,25 @@ enum Commands { Telemetry(cmds::telemetry::Args), } +impl Commands { + fn name(&self) -> &'static str { + match self { + Commands::Init(_) => "init", + Commands::Invoke(_) => "invoke", + Commands::Devnet(_) => "devnet", + Commands::Explore(_) => "explore", + Commands::Bindgen(_) => "bindgen", + Commands::Check(_) => "check", + Commands::Inspect(_) => "inspect", + Commands::Test(_) => "test", + Commands::Build(_) => "build", + Commands::Wallet(_) => "wallet", + Commands::Publish(_) => "publish", + Commands::Telemetry(_) => "telemetry", + } + } +} + pub fn load_config() -> Result> { let current_dir = std::env::current_dir().into_diagnostic()?; @@ -87,7 +107,9 @@ async fn main() -> Result<()> { global::ensure_global_config()?; - match config { + let command_name = cli.command.name(); + + let result = match config { Some(config) => match cli.command { Commands::Init(args) => cmds::init::run(args, Some(&config)), Commands::Invoke(args) => cmds::devnet::invoke::run(args, &config), @@ -107,5 +129,10 @@ async fn main() -> Result<()> { Commands::Telemetry(args) => cmds::telemetry::run(args), _ => Err(miette::miette!("No trix.toml found in current directory")), }, - } + }; + + // Report command result (success or error) + telemetry::report_command_result(command_name, &result.is_ok()); + + result } diff --git a/src/telemetry.rs b/src/telemetry.rs new file mode 100644 index 0000000..e159518 --- /dev/null +++ b/src/telemetry.rs @@ -0,0 +1,260 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use miette::IntoDiagnostic; + +use opentelemetry::{global, KeyValue}; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::Resource; + +pub const DEFAULT_TELEMETRY_ENDPOINT: &str = "http://localhost:4318/v1/metrics"; + +fn init_telemetry(endpoint: &str) -> miette::Result { + // Initialize OTLP exporter using HTTP binary protocol + let exporter = opentelemetry_otlp::MetricExporter::builder() + .with_http() + .with_protocol(opentelemetry_otlp::Protocol::HttpBinary) + .with_endpoint(endpoint) + .build().into_diagnostic()?; + + let resource = Resource::builder() + .with_attribute(KeyValue::new("service.name", "trix")) + .with_attribute(KeyValue::new("service.version", env!("CARGO_PKG_VERSION"))) + .build(); + + // Create a meter provider with the OTLP Metric exporter + let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder() + .with_periodic_exporter(exporter) + .with_resource(resource) + .build(); + global::set_meter_provider(meter_provider.clone()); + + Ok(meter_provider) +} + +/// Report command execution metrics to OpenTelemetry +async fn report_command_execution(command: &str, status: &str) -> miette::Result<()> { + let config = crate::global::read_config()?; + + if !config.telemetry.enabled { + return Ok(()); + } + + let user_fingerprint = get_user_fingerprint()?; + + let otlp_endpoint = config.telemetry.otlp_endpoint.unwrap_or_else(|| DEFAULT_TELEMETRY_ENDPOINT.to_string()); + // Send command execution metric + send_command_metric(&otlp_endpoint, command, &user_fingerprint, status).await?; + + Ok(()) +} + +/// Send command metrics to OpenTelemetry Collector in OTLP format +async fn send_command_metric(endpoint: &str, command: &str, user_fingerprint: &str, status: &str) -> miette::Result<()> { + let meter_provider = init_telemetry(endpoint)?; + let meter = global::meter("trix"); + + let exec_cmd_counter = meter + .u64_counter("command_executed") + .with_description("Counts the number of executed commands") + .build(); + + exec_cmd_counter.add(1, &vec![ + KeyValue::new("command", command.to_string()), + KeyValue::new("user_fingerprint", user_fingerprint.to_string()), + KeyValue::new("status", status.to_string()), + KeyValue::new("version", env!("CARGO_PKG_VERSION").to_string()), + ]); + + let _ = meter_provider.shutdown(); + + Ok(()) +} + +pub fn report_command_result(command: &str, success: &bool) { + if let Ok(config) = crate::global::read_config() { + if config.telemetry.enabled { + let command_owned = command.to_string(); + let status = if *success { "success" } else { "error" }; + send_telemetry_blocking(&command_owned, &status); + } + } +} + +/// Send telemetry in a blocking manner, handling runtime context appropriately +fn send_telemetry_blocking(command: &str, status: &str) { + futures::executor::block_on(async { + tokio::time::timeout( + std::time::Duration::from_secs(3), + report_command_execution(&command, &status) + ).await + }).ok(); +} + +/// Generates an anonymous user fingerprint based on system information +/// This function prioritizes anonymity by using hardware-specific identifiers +/// rather than user-identifiable information like usernames or hostnames +pub fn generate_user_fingerprint() -> miette::Result { + let mut hasher = DefaultHasher::new(); + let mut entropy_sources = 0; + + // Add a salt to prevent rainbow table attacks + "trix-anonymous-user-fingerprint-v1".hash(&mut hasher); + + // Platform-specific hardware identifiers (most anonymous) + #[cfg(target_os = "macos")] + { + // Try to get hardware UUID (hardware-specific, not user-specific) + if let Ok(output) = std::process::Command::new("system_profiler") + .arg("SPHardwareDataType") + .output() + { + if let Ok(hardware_info) = String::from_utf8(output.stdout) { + if let Some(uuid_line) = hardware_info + .lines() + .find(|line| line.contains("Hardware UUID")) + { + uuid_line.hash(&mut hasher); + entropy_sources += 1; + } + } + } + + // Try to get system serial number + if let Ok(output) = std::process::Command::new("system_profiler") + .arg("SPHardwareDataType") + .output() + { + if let Ok(hardware_info) = String::from_utf8(output.stdout) { + if let Some(serial_line) = hardware_info + .lines() + .find(|line| line.contains("Serial Number")) + { + serial_line.hash(&mut hasher); + entropy_sources += 1; + } + } + } + } + + #[cfg(target_os = "linux")] + { + // Machine ID is hardware/installation specific, not user specific + if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id") + .or_else(|_| std::fs::read_to_string("/var/lib/dbus/machine-id")) + { + machine_id.trim().hash(&mut hasher); + entropy_sources += 1; + } + + // Try to get DMI product UUID + if let Ok(product_uuid) = std::fs::read_to_string("/sys/class/dmi/id/product_uuid") { + product_uuid.trim().hash(&mut hasher); + entropy_sources += 1; + } + + // CPU info can provide hardware-specific entropy + if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") { + // Extract only hardware-specific lines, avoiding frequencies that may vary + for line in cpuinfo.lines() { + if line.starts_with("processor") || + line.starts_with("vendor_id") || + line.starts_with("cpu family") || + line.starts_with("model") || + line.starts_with("microcode") { + line.hash(&mut hasher); + } + } + entropy_sources += 1; + } + } + + #[cfg(target_os = "windows")] + { + // Windows hardware UUID + if let Ok(output) = std::process::Command::new("wmic") + .args(["csproduct", "get", "UUID", "/value"]) + .output() + { + if let Ok(uuid_info) = String::from_utf8(output.stdout) { + uuid_info.hash(&mut hasher); + entropy_sources += 1; + } + } + + // Motherboard serial number + if let Ok(output) = std::process::Command::new("wmic") + .args(["baseboard", "get", "SerialNumber", "/value"]) + .output() + { + if let Ok(serial_info) = String::from_utf8(output.stdout) { + serial_info.hash(&mut hasher); + entropy_sources += 1; + } + } + } + + // If we couldn't get enough hardware-specific entropy, use filesystem info + // This is less ideal but still reasonably anonymous + if entropy_sources < 2 { + // Get filesystem creation time or similar system-specific info + if let Ok(metadata) = std::fs::metadata("/") { + if let Ok(created) = metadata.created() { + created.duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default().as_secs().hash(&mut hasher); + entropy_sources += 1; + } + } + + // Add some environment entropy that's less identifying + let env_entropy: Vec = std::env::vars() + .filter_map(|(key, value)| { + // Only use system-level env vars, avoid user-specific ones + if key.starts_with("LC_") || + key == "LANG" || + key == "PATH" || + key == "SHELL" || + key.starts_with("XDG_") { + Some(format!("{}={}", key, value)) + } else { + None + } + }) + .collect(); + + for env_var in env_entropy { + env_var.hash(&mut hasher); + } + entropy_sources += 1; + } + + // Final fallback: generate a random component and store it persistently + // This ensures we always have a stable identifier even if hardware info is unavailable + if entropy_sources == 0 { + return Err(miette::miette!("Unable to generate anonymous fingerprint: insufficient entropy sources")); + } + + // Generate final hash using Rust's standard hasher + let result = hasher.finish(); + + // Convert to hex string for readability + Ok(format!("{:016x}", result)) +} + +/// Ensures that the telemetry configuration has a user fingerprint +pub fn ensure_user_fingerprint() -> miette::Result { + let mut config = crate::global::read_config()?; + + if let Some(ref user_id) = config.telemetry.user_fingerprint { + Ok(user_id.clone()) + } else { + let user_id = generate_user_fingerprint()?; + config.telemetry.user_fingerprint = Some(user_id.clone()); + crate::global::save_config(&config)?; + Ok(user_id) + } +} + +/// Gets the current user fingerprint, generating one if it doesn't exist +pub fn get_user_fingerprint() -> miette::Result { + ensure_user_fingerprint() +} From 41db5a64203bd83bcf59822e5b2d99b5f7f393c8 Mon Sep 17 00:00:00 2001 From: Maximiliano Duthey Date: Fri, 22 Aug 2025 11:02:10 -0300 Subject: [PATCH 2/3] feat: update telemetry configuration handling and initialization --- src/global.rs | 9 +++++---- src/main.rs | 7 ++++++- src/telemetry.rs | 19 +++++-------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/global.rs b/src/global.rs index b32a616..3352bb7 100644 --- a/src/global.rs +++ b/src/global.rs @@ -23,13 +23,14 @@ impl Default for TelemetryConfig { } } -pub fn ensure_global_config() -> miette::Result<()> { +pub fn ensure_global_config() -> miette::Result { let mut trix_path = crate::home::tx3_dir()?; trix_path.push("trix/config.toml"); + let mut config = Config::default(); + if !trix_path.exists() { std::fs::create_dir_all(trix_path.parent().unwrap()).into_diagnostic()?; - let mut config = Config::default(); // Generate user fingerprint when creating config for the first time config.telemetry.user_fingerprint = Some(crate::telemetry::generate_user_fingerprint()?); config.telemetry.otlp_endpoint = Some(crate::telemetry::DEFAULT_TELEMETRY_ENDPOINT.to_string()); @@ -37,14 +38,14 @@ pub fn ensure_global_config() -> miette::Result<()> { print_telemetry_info(); } else { // Ensure existing config has a user fingerprint and otlp_endpoint - let mut config = read_config()?; + config = read_config()?; if config.telemetry.user_fingerprint.is_none() { config.telemetry.user_fingerprint = Some(crate::telemetry::generate_user_fingerprint()?); save_config(&config)?; } } - Ok(()) + Ok(config) } pub fn print_telemetry_info() { diff --git a/src/main.rs b/src/main.rs index 9dc5f93..a8f97e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,7 +105,10 @@ async fn main() -> Result<()> { let config = load_config()?; - global::ensure_global_config()?; + let global_config = global::ensure_global_config()?; + + // init + let meter_provider = telemetry::init_telemetry(global_config.telemetry.otlp_endpoint); let command_name = cli.command.name(); @@ -134,5 +137,7 @@ async fn main() -> Result<()> { // Report command result (success or error) telemetry::report_command_result(command_name, &result.is_ok()); + let _ = meter_provider.map(|mp| mp.shutdown()); + result } diff --git a/src/telemetry.rs b/src/telemetry.rs index e159518..c2a537b 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -8,12 +8,13 @@ use opentelemetry_sdk::Resource; pub const DEFAULT_TELEMETRY_ENDPOINT: &str = "http://localhost:4318/v1/metrics"; -fn init_telemetry(endpoint: &str) -> miette::Result { +pub fn init_telemetry(endpoint: Option) -> miette::Result { + let otlp_endpoint = endpoint.unwrap_or_else(|| DEFAULT_TELEMETRY_ENDPOINT.to_string()); // Initialize OTLP exporter using HTTP binary protocol let exporter = opentelemetry_otlp::MetricExporter::builder() .with_http() .with_protocol(opentelemetry_otlp::Protocol::HttpBinary) - .with_endpoint(endpoint) + .with_endpoint(otlp_endpoint) .build().into_diagnostic()?; let resource = Resource::builder() @@ -33,24 +34,16 @@ fn init_telemetry(endpoint: &str) -> miette::Result miette::Result<()> { - let config = crate::global::read_config()?; - - if !config.telemetry.enabled { - return Ok(()); - } - let user_fingerprint = get_user_fingerprint()?; - let otlp_endpoint = config.telemetry.otlp_endpoint.unwrap_or_else(|| DEFAULT_TELEMETRY_ENDPOINT.to_string()); // Send command execution metric - send_command_metric(&otlp_endpoint, command, &user_fingerprint, status).await?; + send_command_metric(command, &user_fingerprint, status).await?; Ok(()) } /// Send command metrics to OpenTelemetry Collector in OTLP format -async fn send_command_metric(endpoint: &str, command: &str, user_fingerprint: &str, status: &str) -> miette::Result<()> { - let meter_provider = init_telemetry(endpoint)?; +async fn send_command_metric(command: &str, user_fingerprint: &str, status: &str) -> miette::Result<()> { let meter = global::meter("trix"); let exec_cmd_counter = meter @@ -65,8 +58,6 @@ async fn send_command_metric(endpoint: &str, command: &str, user_fingerprint: &s KeyValue::new("version", env!("CARGO_PKG_VERSION").to_string()), ]); - let _ = meter_provider.shutdown(); - Ok(()) } From bcec742ba132fdf4edc9534d8c18e90dfc213140 Mon Sep 17 00:00:00 2001 From: Maximiliano Duthey Date: Mon, 25 Aug 2025 12:56:14 -0300 Subject: [PATCH 3/3] feat: initialize telemetry only if is enabled on config --- src/main.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index a8f97e0..ca3cdb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,9 +108,17 @@ async fn main() -> Result<()> { let global_config = global::ensure_global_config()?; // init - let meter_provider = telemetry::init_telemetry(global_config.telemetry.otlp_endpoint); + let mut meter_provider: Option = None; + + if global_config.telemetry.enabled { + let result = telemetry::init_telemetry(global_config.telemetry.otlp_endpoint); + meter_provider = match result { + Ok(provider) => Some(provider), + Err(_) => None + }; + } - let command_name = cli.command.name(); + let command_name: &'static str = cli.command.name(); let result = match config { Some(config) => match cli.command { @@ -134,10 +142,13 @@ async fn main() -> Result<()> { }, }; - // Report command result (success or error) - telemetry::report_command_result(command_name, &result.is_ok()); - - let _ = meter_provider.map(|mp| mp.shutdown()); + // If it's none is because failure or telemetry is off + if let Some(meter_provider) = meter_provider { + // Report command result (success or error) + telemetry::report_command_result(command_name, &result.is_ok()); + + let _ = meter_provider.shutdown(); + } result }