diff --git a/Cargo.lock b/Cargo.lock index 19233f30071..31320fbfdc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2997,6 +2997,15 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -3014,7 +3023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -3026,10 +3035,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.59.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -3037,7 +3058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -4033,7 +4054,7 @@ dependencies = [ "sway-features", "sway-types", "sway-utils", - "sysinfo", + "sysinfo 0.29.11", "tar", "tempfile", "tokio", @@ -4108,6 +4129,7 @@ name = "forc-tracing" version = "0.69.1" dependencies = [ "ansiterm", + "fuel-telemetry", "regex", "tracing", "tracing-subscriber", @@ -4937,6 +4959,40 @@ version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca73c81646409e9cacac532ff790567d629bc6c512c10ff986536e2335de22c9" +[[package]] +name = "fuel-telemetry" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b6ff154c1f80b4b1a0ca31a4bf1883f48d30eef66ee2c47917b05ab9c44c5c" +dependencies = [ + "base64 0.22.1", + "chrono", + "dirs 6.0.0", + "fuel-telemetry-macros", + "influxdb-line-protocol", + "libc", + "nix 0.29.0", + "regex", + "reqwest 0.12.22", + "sysinfo 0.33.1", + "thiserror 2.0.12", + "tracing", + "tracing-appender", + "tracing-subscriber", + "uuid 1.17.0", +] + +[[package]] +name = "fuel-telemetry-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81c8bba9a5be76590fabfb8741f9027bf593710ecce22d13e1b404c1d4ef692" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "fuel-tx" version = "0.56.0" @@ -6059,7 +6115,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.57.0", ] [[package]] @@ -6214,7 +6270,7 @@ dependencies = [ "rtnetlink", "system-configuration 0.6.1", "tokio", - "windows", + "windows 0.53.0", ] [[package]] @@ -6385,6 +6441,19 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "influxdb-line-protocol" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fa7ee6be451ea0b1912b962c91c8380835e97cf1584a77e18264e908448dcb" +dependencies = [ + "bytes", + "log", + "nom", + "smallvec", + "snafu", +] + [[package]] name = "inout" version = "0.1.4" @@ -9316,6 +9385,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -9461,6 +9541,7 @@ dependencies = [ "cookie", "cookie_store", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.11", @@ -11080,7 +11161,7 @@ dependencies = [ "sway-types", "sway-utils", "swayfmt", - "sysinfo", + "sysinfo 0.29.11", "thiserror 1.0.69", "toml 0.8.23", "tracing", @@ -11372,6 +11453,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -12273,6 +12368,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.30" @@ -13132,6 +13239,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.53.0" @@ -13144,22 +13261,21 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result 0.3.4", - "windows-strings", + "windows-result 0.1.2", + "windows-targets 0.52.6", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", @@ -13168,9 +13284,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a17a823f34a..5ac04c1c8ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,9 @@ fuel-types = "0.62" fuel-tx = "0.62" fuel-vm = "0.62" +# Dependencies for Fuel Telemetry +fuel-telemetry = "0.1" + # Dependencies from the `forc-wallet` repository: forc-wallet = "0.15" diff --git a/forc-plugins/forc-publish/src/main.rs b/forc-plugins/forc-publish/src/main.rs index af51a9fb4fb..9c1b1dc4934 100644 --- a/forc-plugins/forc-publish/src/main.rs +++ b/forc-plugins/forc-publish/src/main.rs @@ -26,7 +26,8 @@ pub struct Opt { #[tokio::main] async fn main() { - init_tracing_subscriber(TracingSubscriberOptions::default()); + let tracing_options = TracingSubscriberOptions::default(); + init_tracing_subscriber(tracing_options.clone()); if let Err(err) = run().await { println!(); diff --git a/forc-tracing/Cargo.toml b/forc-tracing/Cargo.toml index dd28e808c21..3a2b9c0b28b 100644 --- a/forc-tracing/Cargo.toml +++ b/forc-tracing/Cargo.toml @@ -8,8 +8,13 @@ homepage.workspace = true license.workspace = true repository.workspace = true +[features] +default = [] +telemetry = ["dep:fuel-telemetry"] + [dependencies] ansiterm.workspace = true +fuel-telemetry = { workspace = true, optional = true } regex.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["ansi", "env-filter", "json"] } diff --git a/forc-tracing/src/lib.rs b/forc-tracing/src/lib.rs index 3423923895b..8eca6ee3400 100644 --- a/forc-tracing/src/lib.rs +++ b/forc-tracing/src/lib.rs @@ -1,5 +1,8 @@ //! Utility items shared between forc crates. +#[cfg(feature = "telemetry")] +pub mod telemetry; + use ansiterm::Colour; use std::str; use std::sync::atomic::{AtomicBool, Ordering}; @@ -7,21 +10,56 @@ use std::{env, io}; use tracing::{Level, Metadata}; pub use tracing_subscriber::{ self, - filter::{filter_fn, EnvFilter, LevelFilter}, + filter::{filter_fn, EnvFilter, FilterExt, LevelFilter}, fmt::{format::FmtSpan, MakeWriter}, - layer::SubscriberExt, + layer::{Layer, SubscriberExt}, + registry, + util::SubscriberInitExt, + Layer as LayerTrait, }; +#[cfg(feature = "telemetry")] +use fuel_telemetry::WorkerGuard; + const ACTION_COLUMN_WIDTH: usize = 12; +// Thread-local storage for telemetry WorkerGuard +#[cfg(feature = "telemetry")] +thread_local! { + static TELEMETRY_GUARD: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; +} + +/// Filter to hide telemetry spans from regular application logs +#[derive(Clone)] +pub struct HideTelemetryFilter; + +impl tracing_subscriber::layer::Filter for HideTelemetryFilter { + fn enabled( + &self, + meta: &Metadata<'_>, + _cx: &tracing_subscriber::layer::Context<'_, S>, + ) -> bool { + // Hide spans that are created by telemetry macros + !meta.target().starts_with("fuel_telemetry") + } +} + // Global flag to track if JSON output mode is active static JSON_MODE_ACTIVE: AtomicBool = AtomicBool::new(false); +// Global flag to track if telemetry is disabled +static TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false); + /// Check if JSON mode is currently active fn is_json_mode_active() -> bool { JSON_MODE_ACTIVE.load(Ordering::SeqCst) } +/// Check if telemetry is disabled +pub fn is_telemetry_disabled() -> bool { + TELEMETRY_DISABLED.load(Ordering::SeqCst) +} + /// Returns the indentation for the action prefix relative to [ACTION_COLUMN_WIDTH]. fn get_action_indentation(action: &str) -> String { if action.len() < ACTION_COLUMN_WIDTH { @@ -181,7 +219,7 @@ pub fn println_red_err(txt: &str) { const LOG_FILTER: &str = "RUST_LOG"; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub enum TracingWriter { /// Write ERROR and WARN to stderr and everything else to stdout. Stdio, @@ -193,13 +231,14 @@ pub enum TracingWriter { Json, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct TracingSubscriberOptions { pub verbosity: Option, pub silent: Option, pub log_level: Option, pub writer_mode: Option, pub regex_filter: Option, + pub disable_telemetry: Option, } // This allows us to write ERROR and WARN level logs to stderr and everything else to stdout. @@ -232,7 +271,7 @@ impl<'a> MakeWriter<'a> for TracingWriter { } } -/// A subscriber built from default `tracing_subscriber::fmt::SubscriberBuilder` such that it would match directly using `println!` throughout the repo. +/// A subscriber built using tracing_subscriber::registry with optional telemetry layer. /// /// `RUST_LOG` environment variable can be used to set different minimum level for the subscriber, default is `INFO`. pub fn init_tracing_subscriber(options: TracingSubscriberOptions) { @@ -265,19 +304,17 @@ pub fn init_tracing_subscriber(options: TracingSubscriberOptions) { _ => TracingWriter::Stdio, }; - let builder = tracing_subscriber::fmt::Subscriber::builder() - .with_env_filter(env_filter) - .with_ansi(true) - .with_level(false) - .with_file(false) - .with_line_number(false) - .without_time() - .with_target(false) - .with_writer(writer_mode); + // Set the global telemetry disabled flag + let disabled = is_telemetry_disabled_from_options(&options); + TELEMETRY_DISABLED.store(disabled, Ordering::SeqCst); + + // Build the fmt layer with proper filtering + let hide_telemetry_filter = HideTelemetryFilter; // Use regex to filter logs - if provided; otherwise allow all logs - let filter = filter_fn(move |metadata| { - if let Some(ref regex_filter) = options.regex_filter { + let regex_filter = options.regex_filter.clone(); + let regex_filter_fn = filter_fn(move |metadata| { + if let Some(ref regex_filter) = regex_filter { let regex = regex::Regex::new(regex_filter).unwrap(); regex.is_match(metadata.target()) } else { @@ -285,26 +322,173 @@ pub fn init_tracing_subscriber(options: TracingSubscriberOptions) { } }); + // Create filters for non-telemetry mode + let composite_filter = env_filter.and(hide_telemetry_filter).and(regex_filter_fn); + + // Initialize registry with explicit layer handling + #[cfg(feature = "telemetry")] + { + if !disabled { + if let Ok((telemetry_layer, guard)) = fuel_telemetry::new_with_watchers!() { + // Store the WorkerGuard in thread-local storage + TELEMETRY_GUARD.with(|g| { + *g.borrow_mut() = Some(guard); + }); + + // Use the HideTelemetryFilter to prevent telemetry spans from appearing in fmt output + let hide_filter = HideTelemetryFilter; + + match (is_json_mode_active(), level_filter) { + (true, Some(level)) => { + registry() + .with(telemetry_layer) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(hide_filter) + .with_filter(level), + ) + .init(); + return; + } + (true, None) => { + registry() + .with(telemetry_layer) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(hide_filter), + ) + .init(); + return; + } + (false, Some(level)) => { + registry() + .with(telemetry_layer) + .with( + tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(hide_filter) + .with_filter(level), + ) + .init(); + return; + } + (false, None) => { + registry() + .with(telemetry_layer) + .with( + tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(hide_filter), + ) + .init(); + return; + } + } + } + } + } + + // Fallback: no telemetry layer match (is_json_mode_active(), level_filter) { (true, Some(level)) => { - let subscriber = builder.json().with_max_level(level).finish().with(filter); - tracing::subscriber::set_global_default(subscriber).expect("setting subscriber failed"); + registry() + .with( + tracing_subscriber::fmt::layer() + .json() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(composite_filter) + .with_filter(level), + ) + .init(); } (true, None) => { - let subscriber = builder.json().finish().with(filter); - tracing::subscriber::set_global_default(subscriber).expect("setting subscriber failed"); + registry() + .with( + tracing_subscriber::fmt::layer() + .json() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(composite_filter), + ) + .init(); } (false, Some(level)) => { - let subscriber = builder.with_max_level(level).finish().with(filter); - tracing::subscriber::set_global_default(subscriber).expect("setting subscriber failed"); + registry() + .with( + tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(composite_filter) + .with_filter(level), + ) + .init(); } (false, None) => { - let subscriber = builder.finish().with(filter); - tracing::subscriber::set_global_default(subscriber).expect("setting subscriber failed"); + registry() + .with( + tracing_subscriber::fmt::layer() + .with_ansi(true) + .with_level(false) + .with_file(false) + .with_line_number(false) + .without_time() + .with_target(false) + .with_writer(writer_mode) + .with_filter(composite_filter), + ) + .init(); } } } +fn is_telemetry_disabled_from_options(options: &TracingSubscriberOptions) -> bool { + options.disable_telemetry.unwrap_or(false) || env::var("FORC_DISABLE_TELEMETRY").is_ok() +} + #[cfg(test)] mod tests { use super::*; diff --git a/forc-tracing/src/telemetry.rs b/forc-tracing/src/telemetry.rs new file mode 100644 index 00000000000..5e70cad280f --- /dev/null +++ b/forc-tracing/src/telemetry.rs @@ -0,0 +1,72 @@ +//! Telemetry utilities for logging to InfluxDB + +// When telemetry feature is enabled, re-export all fuel_telemetry macros +#[cfg(feature = "telemetry")] +pub use fuel_telemetry::{ + debug_telemetry, error_telemetry, info_telemetry, span_telemetry, trace_telemetry, + warn_telemetry, +}; + +// When telemetry feature is disabled, provide stub macros that trigger compile-time errors +#[cfg(not(feature = "telemetry"))] +pub use self::disabled_telemetry::*; + +#[cfg(not(feature = "telemetry"))] +mod disabled_telemetry { + /// Triggers a compile-time error when telemetry is not enabled + #[macro_export] + macro_rules! telemetry_disabled { + () => { + compile_error!( + "Telemetry is disabled. Enable the 'telemetry' feature to use telemetry macros." + ) + }; + } + + #[macro_export] + macro_rules! error_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + #[macro_export] + macro_rules! warn_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + #[macro_export] + macro_rules! info_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + #[macro_export] + macro_rules! debug_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + #[macro_export] + macro_rules! trace_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + #[macro_export] + macro_rules! span_telemetry { + ($($arg:tt)*) => { + telemetry_disabled!() + }; + } + + pub use { + debug_telemetry, error_telemetry, info_telemetry, span_telemetry, trace_telemetry, + warn_telemetry, + }; +} diff --git a/forc/Cargo.toml b/forc/Cargo.toml index 9ea33b2d127..ef9fe7ea0a6 100644 --- a/forc/Cargo.toml +++ b/forc/Cargo.toml @@ -31,7 +31,7 @@ clap_complete.workspace = true clap_complete_fig.workspace = true forc-pkg.workspace = true forc-test.workspace = true -forc-tracing.workspace = true +forc-tracing = { workspace = true, features = ["telemetry"] } forc-util = { workspace = true, features = ["tx"] } fs_extra.workspace = true fuel-abi-types.workspace = true diff --git a/forc/src/cli/mod.rs b/forc/src/cli/mod.rs index bb8bb98f086..92be84cb2ad 100644 --- a/forc/src/cli/mod.rs +++ b/forc/src/cli/mod.rs @@ -62,6 +62,10 @@ struct Opt { /// Set the log level #[clap(short='L', long, global = true, value_parser = LevelFilter::from_str)] log_level: Option, + + /// Disable telemetry + #[clap(long, global = true)] + disable_telemetry: bool, } #[derive(Subcommand, Debug)] @@ -127,10 +131,11 @@ pub async fn run_cli() -> ForcResult<()> { verbosity: Some(opt.verbose), silent: Some(opt.silent), log_level: opt.log_level, + disable_telemetry: Some(opt.disable_telemetry), ..Default::default() }; - init_tracing_subscriber(tracing_options); + init_tracing_subscriber(tracing_options.clone()); match opt.command { Forc::Add(command) => add::exec(command),