11//! Utility items shared between forc crates.
22
3+ #[ cfg( feature = "telemetry" ) ]
4+ pub mod telemetry;
5+
36use ansiterm:: Colour ;
47use std:: str;
58use std:: sync:: atomic:: { AtomicBool , Ordering } ;
69use std:: { env, io} ;
710use tracing:: { Level , Metadata } ;
811pub use tracing_subscriber:: {
912 self ,
10- filter:: { filter_fn, EnvFilter , LevelFilter } ,
13+ filter:: { filter_fn, EnvFilter , FilterExt , LevelFilter } ,
1114 fmt:: { format:: FmtSpan , MakeWriter } ,
12- layer:: SubscriberExt ,
15+ layer:: { Layer , SubscriberExt } ,
16+ registry,
17+ util:: SubscriberInitExt ,
18+ Layer as LayerTrait ,
1319} ;
1420
21+ #[ cfg( feature = "telemetry" ) ]
22+ use fuel_telemetry:: WorkerGuard ;
23+
1524const ACTION_COLUMN_WIDTH : usize = 12 ;
1625
26+ /// Filter to hide telemetry spans from regular application logs
27+ #[ derive( Clone ) ]
28+ pub struct HideTelemetryFilter ;
29+
30+ impl < S > tracing_subscriber:: layer:: Filter < S > for HideTelemetryFilter {
31+ fn enabled (
32+ & self ,
33+ meta : & Metadata < ' _ > ,
34+ _cx : & tracing_subscriber:: layer:: Context < ' _ , S > ,
35+ ) -> bool {
36+ // Hide spans that are created by telemetry macros
37+ !meta. target ( ) . starts_with ( "fuel_telemetry" )
38+ }
39+ }
40+
1741// Global flag to track if JSON output mode is active
1842static JSON_MODE_ACTIVE : AtomicBool = AtomicBool :: new ( false ) ;
1943
44+ // Global flag to track if telemetry is disabled
45+ static TELEMETRY_DISABLED : AtomicBool = AtomicBool :: new ( false ) ;
46+
47+ /// Check if telemetry is disabled
48+ pub fn is_telemetry_disabled ( ) -> bool {
49+ TELEMETRY_DISABLED . load ( Ordering :: SeqCst )
50+ }
51+
2052/// Check if JSON mode is currently active
2153fn is_json_mode_active ( ) -> bool {
2254 JSON_MODE_ACTIVE . load ( Ordering :: SeqCst )
@@ -181,7 +213,7 @@ pub fn println_red_err(txt: &str) {
181213
182214const LOG_FILTER : & str = "RUST_LOG" ;
183215
184- #[ derive( PartialEq , Eq ) ]
216+ #[ derive( PartialEq , Eq , Clone ) ]
185217pub enum TracingWriter {
186218 /// Write ERROR and WARN to stderr and everything else to stdout.
187219 Stdio ,
@@ -193,13 +225,14 @@ pub enum TracingWriter {
193225 Json ,
194226}
195227
196- #[ derive( Default ) ]
228+ #[ derive( Default , Clone ) ]
197229pub struct TracingSubscriberOptions {
198230 pub verbosity : Option < u8 > ,
199231 pub silent : Option < bool > ,
200232 pub log_level : Option < LevelFilter > ,
201233 pub writer_mode : Option < TracingWriter > ,
202234 pub regex_filter : Option < String > ,
235+ pub disable_telemetry : Option < bool > ,
203236}
204237
205238// This allows us to write ERROR and WARN level logs to stderr and everything else to stdout.
@@ -232,15 +265,34 @@ impl<'a> MakeWriter<'a> for TracingWriter {
232265 }
233266}
234267
235- /// A subscriber built from default ` tracing_subscriber::fmt::SubscriberBuilder` such that it would match directly using `println!` throughout the repo .
268+ /// A subscriber built using tracing_subscriber::registry with optional telemetry layer .
236269///
237270/// `RUST_LOG` environment variable can be used to set different minimum level for the subscriber, default is `INFO`.
238- pub fn init_tracing_subscriber ( options : TracingSubscriberOptions ) {
239- let env_filter = match env:: var_os ( LOG_FILTER ) {
240- Some ( _) => EnvFilter :: try_from_default_env ( ) . expect ( "Invalid `RUST_LOG` provided" ) ,
241- None => EnvFilter :: new ( "info" ) ,
242- } ;
243-
271+ ///
272+ /// # Telemetry
273+ ///
274+ /// When the `telemetry` feature is enabled (default), telemetry data is sent to InfluxDB.
275+ /// This can be disabled via:
276+ /// - The `--disable-telemetry` CLI flag
277+ /// - The `FORC_DISABLE_TELEMETRY` environment variable
278+ /// - Setting `disable_telemetry: Some(true)` in options
279+ ///
280+ /// # Return Value
281+ ///
282+ /// Returns `Ok(Some(WorkerGuard))` when telemetry is enabled, which must be kept alive
283+ /// for the duration of the program to ensure telemetry is properly collected.
284+ /// Returns `Ok(None)` when telemetry is disabled.
285+ ///
286+ /// # Example
287+ ///
288+ /// ```ignore
289+ /// let _guard = init_tracing_subscriber(Default::default())?;
290+ /// // Your program code here
291+ /// // The guard is dropped when main() exits, ensuring proper cleanup
292+ /// ```
293+ pub fn init_tracing_subscriber (
294+ options : TracingSubscriberOptions ,
295+ ) -> anyhow:: Result < Option < WorkerGuard > > {
244296 let level_filter = options
245297 . log_level
246298 . or_else ( || {
@@ -262,47 +314,90 @@ pub fn init_tracing_subscriber(options: TracingSubscriberOptions) {
262314 TracingWriter :: Json
263315 }
264316 Some ( TracingWriter :: Stderr ) => TracingWriter :: Stderr ,
317+ Some ( TracingWriter :: Stdout ) => TracingWriter :: Stdout ,
265318 _ => TracingWriter :: Stdio ,
266319 } ;
267320
268- let builder = tracing_subscriber:: fmt:: Subscriber :: builder ( )
269- . with_env_filter ( env_filter)
270- . with_ansi ( true )
271- . with_level ( false )
272- . with_file ( false )
273- . with_line_number ( false )
274- . without_time ( )
275- . with_target ( false )
276- . with_writer ( writer_mode) ;
277-
278- // Use regex to filter logs - if provided; otherwise allow all logs
279- let filter = filter_fn ( move |metadata| {
280- if let Some ( ref regex_filter) = options. regex_filter {
281- let regex = regex:: Regex :: new ( regex_filter) . unwrap ( ) ;
282- regex. is_match ( metadata. target ( ) )
283- } else {
284- true
285- }
286- } ) ;
321+ // Set the global telemetry disabled flag
322+ let disabled = is_telemetry_disabled_from_options ( & options) ;
323+ TELEMETRY_DISABLED . store ( disabled, Ordering :: SeqCst ) ;
324+
325+ // Build the fmt layer with proper filtering
326+ let hide_telemetry_filter = HideTelemetryFilter ;
327+ let regex_filter = options. regex_filter . clone ( ) ;
328+
329+ macro_rules! init_registry {
330+ ( $registry: expr) => { {
331+ let env_filter = match env:: var_os( LOG_FILTER ) {
332+ Some ( _) => EnvFilter :: try_from_default_env( ) . expect( "Invalid `RUST_LOG` provided" ) ,
333+ None => EnvFilter :: new( "info" ) ,
334+ } ;
335+
336+ let regex_filter_fn = filter_fn( move |metadata| {
337+ if let Some ( ref regex_filter) = regex_filter {
338+ let regex = regex:: Regex :: new( regex_filter) . unwrap( ) ;
339+ regex. is_match( metadata. target( ) )
340+ } else {
341+ true
342+ }
343+ } ) ;
344+
345+ let composite_filter = env_filter. and( hide_telemetry_filter) . and( regex_filter_fn) ;
346+
347+ // Only apply level_filter if user explicitly set it via CLI flags
348+ if is_json_mode_active( ) {
349+ let layer = tracing_subscriber:: fmt:: layer( )
350+ . json( )
351+ . with_ansi( true )
352+ . with_level( false )
353+ . with_file( false )
354+ . with_line_number( false )
355+ . without_time( )
356+ . with_target( false )
357+ . with_writer( writer_mode)
358+ . with_filter( composite_filter) ;
359+
360+ match level_filter {
361+ Some ( filter) => $registry. with( layer. with_filter( filter) ) . init( ) ,
362+ None => $registry. with( layer) . init( ) ,
363+ }
364+ } else {
365+ let layer = tracing_subscriber:: fmt:: layer( )
366+ . with_ansi( true )
367+ . with_level( false )
368+ . with_file( false )
369+ . with_line_number( false )
370+ . without_time( )
371+ . with_target( false )
372+ . with_writer( writer_mode)
373+ . with_filter( composite_filter) ;
374+
375+ match level_filter {
376+ Some ( filter) => $registry. with( layer. with_filter( filter) ) . init( ) ,
377+ None => $registry. with( layer) . init( ) ,
378+ }
379+ }
380+ } } ;
381+ }
287382
288- match ( is_json_mode_active ( ) , level_filter) {
289- ( true , Some ( level) ) => {
290- let subscriber = builder. json ( ) . with_max_level ( level) . finish ( ) . with ( filter) ;
291- tracing:: subscriber:: set_global_default ( subscriber) . expect ( "setting subscriber failed" ) ;
292- }
293- ( true , None ) => {
294- let subscriber = builder. json ( ) . finish ( ) . with ( filter) ;
295- tracing:: subscriber:: set_global_default ( subscriber) . expect ( "setting subscriber failed" ) ;
296- }
297- ( false , Some ( level) ) => {
298- let subscriber = builder. with_max_level ( level) . finish ( ) . with ( filter) ;
299- tracing:: subscriber:: set_global_default ( subscriber) . expect ( "setting subscriber failed" ) ;
300- }
301- ( false , None ) => {
302- let subscriber = builder. finish ( ) . with ( filter) ;
303- tracing:: subscriber:: set_global_default ( subscriber) . expect ( "setting subscriber failed" ) ;
383+ // Initialize registry with explicit layer handling
384+ #[ cfg( feature = "telemetry" ) ]
385+ {
386+ if !disabled {
387+ if let Ok ( ( telemetry_layer, guard) ) = fuel_telemetry:: new_with_watchers!( ) {
388+ init_registry ! ( registry( ) . with( telemetry_layer) ) ;
389+ return Ok ( Some ( guard) ) ;
390+ }
304391 }
305392 }
393+
394+ // Fallback: no telemetry layer
395+ init_registry ! ( registry( ) ) ;
396+ Ok ( None )
397+ }
398+
399+ fn is_telemetry_disabled_from_options ( options : & TracingSubscriberOptions ) -> bool {
400+ options. disable_telemetry . unwrap_or ( false ) || env:: var ( "FORC_DISABLE_TELEMETRY" ) . is_ok ( )
306401}
307402
308403#[ cfg( test) ]
0 commit comments