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#[derive(Clone)]
28pub struct HideTelemetryFilter;
29
30impl<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 !meta.target().starts_with("fuel_telemetry")
38 }
39}
40
41static JSON_MODE_ACTIVE: AtomicBool = AtomicBool::new(false);
43
44static TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false);
46
47pub fn is_telemetry_disabled() -> bool {
49 TELEMETRY_DISABLED.load(Ordering::SeqCst)
50}
51
52fn is_json_mode_active() -> bool {
54 JSON_MODE_ACTIVE.load(Ordering::SeqCst)
55}
56
57fn get_action_indentation(action: &str) -> String {
59 if action.len() < ACTION_COLUMN_WIDTH {
60 " ".repeat(ACTION_COLUMN_WIDTH - action.len())
61 } else {
62 String::new()
63 }
64}
65
66enum TextStyle {
67 Plain,
68 Bold,
69 Label(String),
70 Action(String),
71}
72
73enum LogLevel {
74 #[allow(dead_code)]
75 Trace,
76 Debug,
77 Info,
78 Warn,
79 Error,
80}
81
82fn print_message(text: &str, color: Colour, style: TextStyle, level: LogLevel) {
84 let log_msg = match (is_json_mode_active(), style) {
85 (true, TextStyle::Plain | TextStyle::Bold) => text.to_string(),
87 (true, TextStyle::Label(label)) => format!("{label}: {text}"),
88 (true, TextStyle::Action(action)) => {
89 let indent = get_action_indentation(&action);
90 format!("{indent}{action} {text}")
91 }
92
93 (false, TextStyle::Plain) => format!("{}", color.paint(text)),
95 (false, TextStyle::Bold) => format!("{}", color.bold().paint(text)),
96 (false, TextStyle::Label(label)) => format!("{} {}", color.bold().paint(label), text),
97 (false, TextStyle::Action(action)) => {
98 let indent = get_action_indentation(&action);
99 format!("{}{} {}", indent, color.bold().paint(action), text)
100 }
101 };
102
103 match level {
104 LogLevel::Trace => tracing::trace!("{}", log_msg),
105 LogLevel::Debug => tracing::debug!("{}", log_msg),
106 LogLevel::Info => tracing::info!("{}", log_msg),
107 LogLevel::Warn => tracing::warn!("{}", log_msg),
108 LogLevel::Error => tracing::error!("{}", log_msg),
109 }
110}
111
112pub fn println_label_green(label: &str, txt: &str) {
114 print_message(
115 txt,
116 Colour::Green,
117 TextStyle::Label(label.to_string()),
118 LogLevel::Info,
119 );
120}
121
122pub fn println_action_green(action: &str, txt: &str) {
124 println_action(action, txt, Colour::Green);
125}
126
127pub fn println_label_red(label: &str, txt: &str) {
129 print_message(
130 txt,
131 Colour::Red,
132 TextStyle::Label(label.to_string()),
133 LogLevel::Info,
134 );
135}
136
137pub fn println_action_red(action: &str, txt: &str) {
139 println_action(action, txt, Colour::Red);
140}
141
142pub fn println_action_yellow(action: &str, txt: &str) {
144 println_action(action, txt, Colour::Yellow);
145}
146
147fn println_action(action: &str, txt: &str, color: Colour) {
148 print_message(
149 txt,
150 color,
151 TextStyle::Action(action.to_string()),
152 LogLevel::Info,
153 );
154}
155
156pub fn println_warning(txt: &str) {
158 print_message(
159 txt,
160 Colour::Yellow,
161 TextStyle::Label("warning:".to_string()),
162 LogLevel::Warn,
163 );
164}
165
166pub fn println_warning_verbose(txt: &str) {
168 print_message(
169 txt,
170 Colour::Yellow,
171 TextStyle::Label("warning:".to_string()),
172 LogLevel::Debug,
173 );
174}
175
176pub fn println_error(txt: &str) {
178 print_message(
179 txt,
180 Colour::Red,
181 TextStyle::Label("error:".to_string()),
182 LogLevel::Error,
183 );
184}
185
186pub fn println_red(txt: &str) {
187 print_message(txt, Colour::Red, TextStyle::Plain, LogLevel::Info);
188}
189
190pub fn println_green(txt: &str) {
191 print_message(txt, Colour::Green, TextStyle::Plain, LogLevel::Info);
192}
193
194pub fn println_yellow(txt: &str) {
195 print_message(txt, Colour::Yellow, TextStyle::Plain, LogLevel::Info);
196}
197
198pub fn println_green_bold(txt: &str) {
199 print_message(txt, Colour::Green, TextStyle::Bold, LogLevel::Info);
200}
201
202pub fn println_yellow_bold(txt: &str) {
203 print_message(txt, Colour::Yellow, TextStyle::Bold, LogLevel::Info);
204}
205
206pub fn println_yellow_err(txt: &str) {
207 print_message(txt, Colour::Yellow, TextStyle::Plain, LogLevel::Error);
208}
209
210pub fn println_red_err(txt: &str) {
211 print_message(txt, Colour::Red, TextStyle::Plain, LogLevel::Error);
212}
213
214const LOG_FILTER: &str = "RUST_LOG";
215
216#[derive(PartialEq, Eq, Clone)]
217pub enum TracingWriter {
218 Stdio,
220 Stdout,
222 Stderr,
224 Json,
226}
227
228#[derive(Default, Clone)]
229pub struct TracingSubscriberOptions {
230 pub verbosity: Option<u8>,
231 pub silent: Option<bool>,
232 pub log_level: Option<LevelFilter>,
233 pub writer_mode: Option<TracingWriter>,
234 pub regex_filter: Option<String>,
235 pub disable_telemetry: Option<bool>,
236}
237
238impl<'a> MakeWriter<'a> for TracingWriter {
241 type Writer = Box<dyn io::Write>;
242
243 fn make_writer(&'a self) -> Self::Writer {
244 match self {
245 TracingWriter::Stderr => Box::new(io::stderr()),
246 _ => Box::new(io::stdout()),
250 }
251 }
252
253 fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer {
254 if *self == TracingWriter::Stderr
258 || (*self == TracingWriter::Stdio && meta.level() <= &Level::WARN)
259 {
260 return Box::new(io::stderr());
261 }
262
263 Box::new(io::stdout())
265 }
266}
267
268pub fn init_tracing_subscriber(
294 options: TracingSubscriberOptions,
295) -> anyhow::Result<Option<WorkerGuard>> {
296 let level_filter = options
297 .log_level
298 .or_else(|| {
299 options.verbosity.and_then(|verbosity| match verbosity {
300 1 => Some(LevelFilter::DEBUG), 2 => Some(LevelFilter::TRACE), _ => None,
303 })
304 })
305 .or_else(|| {
306 options
307 .silent
308 .and_then(|silent| silent.then_some(LevelFilter::OFF))
309 });
310
311 let writer_mode = match options.writer_mode {
312 Some(TracingWriter::Json) => {
313 JSON_MODE_ACTIVE.store(true, Ordering::SeqCst);
314 TracingWriter::Json
315 }
316 Some(TracingWriter::Stderr) => TracingWriter::Stderr,
317 Some(TracingWriter::Stdout) => TracingWriter::Stdout,
318 _ => TracingWriter::Stdio,
319 };
320
321 let disabled = is_telemetry_disabled_from_options(&options);
323 TELEMETRY_DISABLED.store(disabled, Ordering::SeqCst);
324
325 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 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 }
382
383 #[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 }
391 }
392 }
393
394 init_registry!(registry());
396 Ok(None)
397}
398
399fn is_telemetry_disabled_from_options(options: &TracingSubscriberOptions) -> bool {
400 options.disable_telemetry.unwrap_or(false) || env::var("FORC_DISABLE_TELEMETRY").is_ok()
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use serial_test::serial;
407 use tracing_test::traced_test;
408
409 fn setup_test() {
411 JSON_MODE_ACTIVE.store(false, Ordering::SeqCst);
412 }
413
414 #[traced_test]
415 #[test]
416 #[serial]
417 fn test_println_label_green() {
418 setup_test();
419
420 let txt = "main.sw";
421 println_label_green("Compiling", txt);
422
423 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
424 assert!(logs_contain(&format!("{expected_action} {txt}")));
425 }
426
427 #[traced_test]
428 #[test]
429 #[serial]
430 fn test_println_label_red() {
431 setup_test();
432
433 let txt = "main.sw";
434 println_label_red("Error", txt);
435
436 let expected_action = "\x1b[1;31mError\x1b[0m";
437 assert!(logs_contain(&format!("{expected_action} {txt}")));
438 }
439
440 #[traced_test]
441 #[test]
442 #[serial]
443 fn test_println_action_green() {
444 setup_test();
445
446 let txt = "main.sw";
447 println_action_green("Compiling", txt);
448
449 let expected_action = "\x1b[1;32mCompiling\x1b[0m";
450 assert!(logs_contain(&format!(" {expected_action} {txt}")));
451 }
452
453 #[traced_test]
454 #[test]
455 #[serial]
456 fn test_println_action_green_long() {
457 setup_test();
458
459 let txt = "main.sw";
460 println_action_green("Supercalifragilistic", txt);
461
462 let expected_action = "\x1b[1;32mSupercalifragilistic\x1b[0m";
463 assert!(logs_contain(&format!("{expected_action} {txt}")));
464 }
465
466 #[traced_test]
467 #[test]
468 #[serial]
469 fn test_println_action_red() {
470 setup_test();
471
472 let txt = "main";
473 println_action_red("Removing", txt);
474
475 let expected_action = "\x1b[1;31mRemoving\x1b[0m";
476 assert!(logs_contain(&format!(" {expected_action} {txt}")));
477 }
478
479 #[traced_test]
480 #[test]
481 #[serial]
482 fn test_json_mode_println_functions() {
483 setup_test();
484
485 JSON_MODE_ACTIVE.store(true, Ordering::SeqCst);
486
487 println_label_green("Label", "Value");
489 assert!(logs_contain("Label: Value"));
490
491 println_action_green("Action", "Target");
492 assert!(logs_contain("Action"));
493 assert!(logs_contain("Target"));
494
495 println_green("Green text");
496 assert!(logs_contain("Green text"));
497
498 println_warning("This is a warning");
499 assert!(logs_contain("This is a warning"));
500
501 println_error("This is an error");
502 assert!(logs_contain("This is an error"));
503
504 JSON_MODE_ACTIVE.store(false, Ordering::SeqCst);
505 }
506}