1#[cfg(feature = "telemetry")]
4pub mod telemetry;
5
6use ansiterm::Colour;
7use std::str;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::{env, io};
10use tracing::{Level, Metadata};
11pub use tracing_subscriber::{
12 self,
13 filter::{filter_fn, EnvFilter, FilterExt, LevelFilter},
14 fmt::{format::FmtSpan, MakeWriter},
15 layer::{Layer, SubscriberExt},
16 registry,
17 util::SubscriberInitExt,
18 Layer as LayerTrait,
19};
20
21#[cfg(feature = "telemetry")]
22use fuel_telemetry::WorkerGuard;
23
24const ACTION_COLUMN_WIDTH: usize = 12;
25
26#[cfg(feature = "telemetry")]
28thread_local! {
29 static TELEMETRY_GUARD: std::cell::RefCell<Option<WorkerGuard>> = const { std::cell::RefCell::new(None) };
30}
31
32#[derive(Clone)]
34pub struct HideTelemetryFilter;
35
36impl<S> tracing_subscriber::layer::Filter<S> for HideTelemetryFilter {
37 fn enabled(
38 &self,
39 meta: &Metadata<'_>,
40 _cx: &tracing_subscriber::layer::Context<'_, S>,
41 ) -> bool {
42 !meta.target().starts_with("fuel_telemetry")
44 }
45}
46
47static JSON_MODE_ACTIVE: AtomicBool = AtomicBool::new(false);
49
50static TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false);
52
53fn is_json_mode_active() -> bool {
55 JSON_MODE_ACTIVE.load(Ordering::SeqCst)
56}
57
58pub fn is_telemetry_disabled() -> bool {
60 TELEMETRY_DISABLED.load(Ordering::SeqCst)
61}
62
63fn get_action_indentation(action: &str) -> String {
65 if action.len() < ACTION_COLUMN_WIDTH {
66 " ".repeat(ACTION_COLUMN_WIDTH - action.len())
67 } else {
68 String::new()
69 }
70}
71
72enum TextStyle {
73 Plain,
74 Bold,
75 Label(String),
76 Action(String),
77}
78
79enum LogLevel {
80 #[allow(dead_code)]
81 Trace,
82 Debug,
83 Info,
84 Warn,
85 Error,
86}
87
88fn print_message(text: &str, color: Colour, style: TextStyle, level: LogLevel) {
90 let log_msg = match (is_json_mode_active(), style) {
91 (true, TextStyle::Plain | TextStyle::Bold) => text.to_string(),
93 (true, TextStyle::Label(label)) => format!("{label}: {text}"),
94 (true, TextStyle::Action(action)) => {
95 let indent = get_action_indentation(&action);
96 format!("{indent}{action} {text}")
97 }
98
99 (false, TextStyle::Plain) => format!("{}", color.paint(text)),
101 (false, TextStyle::Bold) => format!("{}", color.bold().paint(text)),
102 (false, TextStyle::Label(label)) => format!("{} {}", color.bold().paint(label), text),
103 (false, TextStyle::Action(action)) => {
104 let indent = get_action_indentation(&action);
105 format!("{}{} {}", indent, color.bold().paint(action), text)
106 }
107 };
108
109 match level {
110 LogLevel::Trace => tracing::trace!("{}", log_msg),
111 LogLevel::Debug => tracing::debug!("{}", log_msg),
112 LogLevel::Info => tracing::info!("{}", log_msg),
113 LogLevel::Warn => tracing::warn!("{}", log_msg),
114 LogLevel::Error => tracing::error!("{}", log_msg),
115 }
116}
117
118pub fn println_label_green(label: &str, txt: &str) {
120 print_message(
121 txt,
122 Colour::Green,
123 TextStyle::Label(label.to_string()),
124 LogLevel::Info,
125 );
126}
127
128pub fn println_action_green(action: &str, txt: &str) {
130 println_action(action, txt, Colour::Green);
131}
132
133pub fn println_label_red(label: &str, txt: &str) {
135 print_message(
136 txt,
137 Colour::Red,
138 TextStyle::Label(label.to_string()),
139 LogLevel::Info,
140 );
141}
142
143pub fn println_action_red(action: &str, txt: &str) {
145 println_action(action, txt, Colour::Red);
146}
147
148pub fn println_action_yellow(action: &str, txt: &str) {
150 println_action(action, txt, Colour::Yellow);
151}
152
153fn println_action(action: &str, txt: &str, color: Colour) {
154 print_message(
155 txt,
156 color,
157 TextStyle::Action(action.to_string()),
158 LogLevel::Info,
159 );
160}
161
162pub fn println_warning(txt: &str) {
164 print_message(
165 txt,
166 Colour::Yellow,
167 TextStyle::Label("warning:".to_string()),
168 LogLevel::Warn,
169 );
170}
171
172pub fn println_warning_verbose(txt: &str) {
174 print_message(
175 txt,
176 Colour::Yellow,
177 TextStyle::Label("warning:".to_string()),
178 LogLevel::Debug,
179 );
180}
181
182pub fn println_error(txt: &str) {
184 print_message(
185 txt,
186 Colour::Red,
187 TextStyle::Label("error:".to_string()),
188 LogLevel::Error,
189 );
190}
191
192pub fn println_red(txt: &str) {
193 print_message(txt, Colour::Red, TextStyle::Plain, LogLevel::Info);
194}
195
196pub fn println_green(txt: &str) {
197 print_message(txt, Colour::Green, TextStyle::Plain, LogLevel::Info);
198}
199
200pub fn println_yellow(txt: &str) {
201 print_message(txt, Colour::Yellow, TextStyle::Plain, LogLevel::Info);
202}
203
204pub fn println_green_bold(txt: &str) {
205 print_message(txt, Colour::Green, TextStyle::Bold, LogLevel::Info);
206}
207
208pub fn println_yellow_bold(txt: &str) {
209 print_message(txt, Colour::Yellow, TextStyle::Bold, LogLevel::Info);
210}
211
212pub fn println_yellow_err(txt: &str) {
213 print_message(txt, Colour::Yellow, TextStyle::Plain, LogLevel::Error);
214}
215
216pub fn println_red_err(txt: &str) {
217 print_message(txt, Colour::Red, TextStyle::Plain, LogLevel::Error);
218}
219
220const LOG_FILTER: &str = "RUST_LOG";
221
222#[derive(PartialEq, Eq, Clone)]
223pub enum TracingWriter {
224 Stdio,
226 Stdout,
228 Stderr,
230 Json,
232}
233
234#[derive(Default, Clone)]
235pub struct TracingSubscriberOptions {
236 pub verbosity: Option<u8>,
237 pub silent: Option<bool>,
238 pub log_level: Option<LevelFilter>,
239 pub writer_mode: Option<TracingWriter>,
240 pub regex_filter: Option<String>,
241 pub disable_telemetry: Option<bool>,
242}
243
244impl<'a> MakeWriter<'a> for TracingWriter {
247 type Writer = Box<dyn io::Write>;
248
249 fn make_writer(&'a self) -> Self::Writer {
250 match self {
251 TracingWriter::Stderr => Box::new(io::stderr()),
252 _ => Box::new(io::stdout()),
256 }
257 }
258
259 fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
260 if *self == TracingWriter::Stderr
264 || (*self == TracingWriter::Stdio && meta.level() <= &Level::WARN)
265 {
266 return Box::new(io::stderr());
267 }
268
269 Box::new(io::stdout())
271 }
272}
273
274pub fn init_tracing_subscriber(options: TracingSubscriberOptions) {
278 let level_filter = options
279 .log_level
280 .or_else(|| {
281 options.verbosity.and_then(|verbosity| match verbosity {
282 1 => Some(LevelFilter::DEBUG), 2 => Some(LevelFilter::TRACE), _ => None,
285 })
286 })
287 .or_else(|| {
288 options
289 .silent
290 .and_then(|silent| silent.then_some(LevelFilter::OFF))
291 })
292 .unwrap_or(LevelFilter::INFO); let writer_mode = match options.writer_mode {
295 Some(TracingWriter::Json) => {
296 JSON_MODE_ACTIVE.store(true, Ordering::SeqCst);
297 TracingWriter::Json
298 }
299 Some(TracingWriter::Stderr) => TracingWriter::Stderr,
300 _ => TracingWriter::Stdio,
301 };
302
303 let disabled = is_telemetry_disabled_from_options(&options);
305 TELEMETRY_DISABLED.store(disabled, Ordering::SeqCst);
306
307 let hide_telemetry_filter = HideTelemetryFilter;
309
310 let regex_filter = options.regex_filter.clone();
312
313 macro_rules! init_registry {
314 ($registry:expr) => {{
315 let env_filter = match env::var_os(LOG_FILTER) {
316 Some(_) => EnvFilter::try_from_default_env().expect("Invalid `RUST_LOG` provided"),
317 None => EnvFilter::new("info"),
318 };
319
320 let regex_filter = regex_filter.clone();
321 let regex_filter_fn = filter_fn(move |metadata| {
322 if let Some(ref regex_filter) = regex_filter {
323 let regex = regex::Regex::new(regex_filter).unwrap();
324 regex.is_match(metadata.target())
325 } else {
326 true
327 }
328 });
329
330 let composite_filter = env_filter.and(hide_telemetry_filter).and(regex_filter_fn);
331
332 if is_json_mode_active() {
333 $registry
334 .with(
335 tracing_subscriber::fmt::layer()
336 .json()
337 .with_ansi(true)
338 .with_level(false)
339 .with_file(false)
340 .with_line_number(false)
341 .without_time()
342 .with_target(false)
343 .with_writer(writer_mode)
344 .with_filter(composite_filter)
345 .with_filter(level_filter),
346 )
347 .init();
348 } else {
349 $registry
350 .with(
351 tracing_subscriber::fmt::layer()
352 .with_ansi(true)
353 .with_level(false)
354 .with_file(false)
355 .with_line_number(false)
356 .without_time()
357 .with_target(false)
358 .with_writer(writer_mode)
359 .with_filter(composite_filter)
360 .with_filter(level_filter),
361 )
362 .init();
363 }
364 }};
365 }
366
367 #[cfg(feature = "telemetry")]
369 {
370 if !disabled {
371 if let Ok((telemetry_layer, guard)) = fuel_telemetry::new_with_watchers!() {
372 TELEMETRY_GUARD.with(|g| {
374 *g.borrow_mut() = Some(guard);
375 });
376
377 init_registry!(registry().with(telemetry_layer));
378 return;
379 }
380 }
381 }
382
383 init_registry!(registry());
385}
386
387fn is_telemetry_disabled_from_options(options: &TracingSubscriberOptions) -> bool {
388 options.disable_telemetry.unwrap_or(false) || env::var("FORC_DISABLE_TELEMETRY").is_ok()
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use tracing_test::traced_test;
395
396 fn setup_test() {
398 JSON_MODE_ACTIVE.store(false, Ordering::SeqCst);
399 }
400
401 #[traced_test]
402 #[test]
403 fn test_println_label_green() {
404 setup_test();
405
406 let txt = "main.sw";
407 println_label_green("Compiling", txt);
408
409 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
410 assert!(logs_contain(&format!("{expected_action} {txt}")));
411 }
412
413 #[traced_test]
414 #[test]
415 fn test_println_label_red() {
416 setup_test();
417
418 let txt = "main.sw";
419 println_label_red("Error", txt);
420
421 let expected_action = "\x1b[1;31mError\x1b[0m";
422 assert!(logs_contain(&format!("{expected_action} {txt}")));
423 }
424
425 #[traced_test]
426 #[test]
427 fn test_println_action_green() {
428 setup_test();
429
430 let txt = "main.sw";
431 println_action_green("Compiling", txt);
432
433 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
434 assert!(logs_contain(&format!(" {expected_action} {txt}")));
435 }
436
437 #[traced_test]
438 #[test]
439 fn test_println_action_green_long() {
440 setup_test();
441
442 let txt = "main.sw";
443 println_action_green("Supercalifragilistic", txt);
444
445 let expected_action = "\x1b[1;32mSupercalifragilistic\x1b[0m";
446 assert!(logs_contain(&format!("{expected_action} {txt}")));
447 }
448
449 #[traced_test]
450 #[test]
451 fn test_println_action_red() {
452 setup_test();
453
454 let txt = "main";
455 println_action_red("Removing", txt);
456
457 let expected_action = "\x1b[1;31mRemoving\x1b[0m";
458 assert!(logs_contain(&format!(" {expected_action} {txt}")));
459 }
460
461 #[traced_test]
462 #[test]
463 fn test_json_mode_println_functions() {
464 setup_test();
465
466 JSON_MODE_ACTIVE.store(true, Ordering::SeqCst);
467
468 println_label_green("Label", "Value");
470 assert!(logs_contain("Label: Value"));
471
472 println_action_green("Action", "Target");
473 assert!(logs_contain("Action"));
474 assert!(logs_contain("Target"));
475
476 println_green("Green text");
477 assert!(logs_contain("Green text"));
478
479 println_warning("This is a warning");
480 assert!(logs_contain("This is a warning"));
481
482 println_error("This is an error");
483 assert!(logs_contain("This is an error"));
484
485 JSON_MODE_ACTIVE.store(false, Ordering::SeqCst);
486 }
487}