1#![warn(missing_docs)]
24
25mod directives;
26mod event_format;
27mod fast_local_time;
28mod layers;
29mod stderr_writer;
30
31pub(crate) type DefaultLogger = stderr_writer::MakeStderrWriter;
32
33pub use directives::*;
34pub use sc_tracing_proc_macro::*;
35
36use is_terminal::IsTerminal;
37use std::io;
38use tracing::Subscriber;
39use tracing_subscriber::{
40 filter::LevelFilter,
41 fmt::{
42 format, FormatEvent, FormatFields, Formatter, Layer as FmtLayer, MakeWriter,
43 SubscriberBuilder,
44 },
45 layer::{self, SubscriberExt},
46 registry::LookupSpan,
47 EnvFilter, FmtSubscriber, Layer, Registry,
48};
49
50pub use event_format::*;
51pub use fast_local_time::FastLocalTime;
52pub use layers::*;
53
54use stderr_writer::MakeStderrWriter;
55
56pub type Result<T> = std::result::Result<T, Error>;
58
59#[derive(Debug, thiserror::Error)]
61#[allow(missing_docs)]
62#[non_exhaustive]
63#[error(transparent)]
64pub enum Error {
65 IoError(#[from] io::Error),
66 SetGlobalDefaultError(#[from] tracing::subscriber::SetGlobalDefaultError),
67 DirectiveParseError(#[from] tracing_subscriber::filter::ParseError),
68 SetLoggerError(#[from] tracing_log::log_tracer::SetLoggerError),
69}
70
71macro_rules! enable_log_reloading {
72 ($builder:expr) => {{
73 let builder = $builder.with_filter_reloading();
74 let handle = builder.reload_handle();
75 set_reload_handle(handle);
76 builder
77 }};
78}
79
80fn to_log_level_filter(level_filter: Option<LevelFilter>) -> log::LevelFilter {
84 match level_filter {
85 Some(LevelFilter::INFO) | None => log::LevelFilter::Info,
86 Some(LevelFilter::TRACE) => log::LevelFilter::Trace,
87 Some(LevelFilter::WARN) => log::LevelFilter::Warn,
88 Some(LevelFilter::ERROR) => log::LevelFilter::Error,
89 Some(LevelFilter::DEBUG) => log::LevelFilter::Debug,
90 Some(LevelFilter::OFF) => log::LevelFilter::Off,
91 }
92}
93
94fn prepare_subscriber<N, E, F, W>(
96 directives: &str,
97 profiling_targets: Option<&str>,
98 force_colors: Option<bool>,
99 detailed_output: bool,
100 builder_hook: impl Fn(
101 SubscriberBuilder<format::DefaultFields, EventFormat, EnvFilter, DefaultLogger>,
102 ) -> SubscriberBuilder<N, E, F, W>,
103) -> Result<impl Subscriber + for<'a> LookupSpan<'a>>
104where
105 N: for<'writer> FormatFields<'writer> + 'static,
106 E: FormatEvent<Registry, N> + 'static,
107 W: for<'writer> MakeWriter<'writer> + 'static,
108 F: layer::Layer<Formatter<N, E, W>> + Send + Sync + 'static,
109 FmtLayer<Registry, N, E, W>: layer::Layer<Registry> + Send + Sync + 'static,
110{
111 fn parse_user_directives(mut env_filter: EnvFilter, dirs: &str) -> Result<EnvFilter> {
113 for dir in dirs.split(',') {
114 env_filter = env_filter.add_directive(parse_default_directive(dir)?);
115 }
116 Ok(env_filter)
117 }
118
119 let mut env_filter = EnvFilter::default()
122 .add_directive(parse_default_directive("info").expect("provided directive is valid"))
124 .add_directive(parse_default_directive("ws=off").expect("provided directive is valid"))
126 .add_directive(parse_default_directive("yamux=off").expect("provided directive is valid"))
127 .add_directive(
128 parse_default_directive("regalloc=off").expect("provided directive is valid"),
129 )
130 .add_directive(
131 parse_default_directive("cranelift_codegen=off").expect("provided directive is valid"),
132 )
133 .add_directive(
135 parse_default_directive("cranelift_wasm=warn").expect("provided directive is valid"),
136 )
137 .add_directive(parse_default_directive("hyper=warn").expect("provided directive is valid"))
138 .add_directive(
139 parse_default_directive("trust_dns_proto=off").expect("provided directive is valid"),
140 )
141 .add_directive(
142 parse_default_directive("hickory_proto=off").expect("provided directive is valid"),
143 )
144 .add_directive(
145 parse_default_directive("libp2p_mdns::behaviour::iface=off")
146 .expect("provided directive is valid"),
147 )
148 .add_directive(
150 parse_default_directive("rustls::common_state=off")
151 .expect("provided directive is valid"),
152 )
153 .add_directive(
154 parse_default_directive("rustls::conn=off").expect("provided directive is valid"),
155 );
156
157 if let Ok(lvl) = std::env::var("RUST_LOG") {
158 if lvl != "" {
159 env_filter = parse_user_directives(env_filter, &lvl)?;
160 }
161 }
162
163 if directives != "" {
164 env_filter = parse_user_directives(env_filter, directives)?;
165 }
166
167 if let Some(profiling_targets) = profiling_targets {
168 env_filter = parse_user_directives(env_filter, profiling_targets)?;
169 env_filter = env_filter.add_directive(
170 parse_default_directive("sc_tracing=trace").expect("provided directive is valid"),
171 );
172 }
173
174 let max_level_hint = Layer::<FmtSubscriber>::max_level_hint(&env_filter);
175 let max_level = to_log_level_filter(max_level_hint);
176
177 tracing_log::LogTracer::builder().with_max_level(max_level).init()?;
178
179 let detailed_output = match max_level_hint {
181 Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => false,
182 _ => true,
183 } || detailed_output;
184
185 let enable_color = force_colors.unwrap_or_else(|| io::stderr().is_terminal());
186 let timer = fast_local_time::FastLocalTime { with_fractional: detailed_output };
187
188 console::set_colors_enabled(enable_color);
190 console::set_colors_enabled_stderr(enable_color);
191
192 let event_format = EventFormat {
193 timer,
194 display_target: detailed_output,
195 display_level: detailed_output,
196 display_thread_name: detailed_output,
197 dup_to_stdout: !io::stderr().is_terminal() && io::stdout().is_terminal(),
198 };
199 let builder = FmtSubscriber::builder().with_env_filter(env_filter);
200
201 let builder = builder.with_span_events(format::FmtSpan::NONE);
202
203 let builder = builder.with_writer(MakeStderrWriter::default());
204
205 let builder = builder.event_format(event_format);
206
207 let builder = builder_hook(builder);
208
209 let subscriber = builder.finish().with(PrefixLayer);
210
211 Ok(subscriber)
212}
213
214pub struct LoggerBuilder {
216 directives: String,
217 profiling: Option<(crate::TracingReceiver, String)>,
218 custom_profiler: Option<Box<dyn crate::TraceHandler>>,
219 log_reloading: bool,
220 force_colors: Option<bool>,
221 detailed_output: bool,
222}
223
224impl LoggerBuilder {
225 pub fn new<S: Into<String>>(directives: S) -> Self {
227 Self {
228 directives: directives.into(),
229 profiling: None,
230 custom_profiler: None,
231 log_reloading: false,
232 force_colors: None,
233 detailed_output: false,
234 }
235 }
236
237 pub fn with_profiling<S: Into<String>>(
239 &mut self,
240 tracing_receiver: crate::TracingReceiver,
241 profiling_targets: S,
242 ) -> &mut Self {
243 self.profiling = Some((tracing_receiver, profiling_targets.into()));
244 self
245 }
246
247 pub fn with_custom_profiling(
249 &mut self,
250 custom_profiler: Box<dyn crate::TraceHandler>,
251 ) -> &mut Self {
252 self.custom_profiler = Some(custom_profiler);
253 self
254 }
255
256 pub fn with_log_reloading(&mut self, enabled: bool) -> &mut Self {
258 self.log_reloading = enabled;
259 self
260 }
261
262 pub fn with_detailed_output(&mut self, detailed: bool) -> &mut Self {
269 self.detailed_output = detailed;
270 self
271 }
272
273 pub fn with_colors(&mut self, enable: bool) -> &mut Self {
275 self.force_colors = Some(enable);
276 self
277 }
278
279 pub fn init(self) -> Result<()> {
283 if let Some((tracing_receiver, profiling_targets)) = self.profiling {
284 if self.log_reloading {
285 let subscriber = prepare_subscriber(
286 &self.directives,
287 Some(&profiling_targets),
288 self.force_colors,
289 self.detailed_output,
290 |builder| enable_log_reloading!(builder),
291 )?;
292 let mut profiling =
293 crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
294
295 self.custom_profiler
296 .into_iter()
297 .for_each(|profiler| profiling.add_handler(profiler));
298
299 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
300
301 Ok(())
302 } else {
303 let subscriber = prepare_subscriber(
304 &self.directives,
305 Some(&profiling_targets),
306 self.force_colors,
307 self.detailed_output,
308 |builder| builder,
309 )?;
310 let mut profiling =
311 crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
312
313 self.custom_profiler
314 .into_iter()
315 .for_each(|profiler| profiling.add_handler(profiler));
316
317 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
318
319 Ok(())
320 }
321 } else if self.log_reloading {
322 let subscriber = prepare_subscriber(
323 &self.directives,
324 None,
325 self.force_colors,
326 self.detailed_output,
327 |builder| enable_log_reloading!(builder),
328 )?;
329
330 tracing::subscriber::set_global_default(subscriber)?;
331
332 Ok(())
333 } else {
334 let subscriber = prepare_subscriber(
335 &self.directives,
336 None,
337 self.force_colors,
338 self.detailed_output,
339 |builder| builder,
340 )?;
341
342 tracing::subscriber::set_global_default(subscriber)?;
343
344 Ok(())
345 }
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use crate as sc_tracing;
353 use log::info;
354 use std::{
355 collections::BTreeMap,
356 env,
357 process::Command,
358 sync::{
359 atomic::{AtomicBool, AtomicUsize, Ordering},
360 Arc,
361 },
362 };
363 use tracing::{metadata::Kind, subscriber::Interest, Callsite, Level, Metadata};
364
365 const EXPECTED_LOG_MESSAGE: &'static str = "yeah logging works as expected";
366 const EXPECTED_NODE_NAME: &'static str = "THE_NODE";
367
368 fn init_logger(directives: &str) {
369 let _ = LoggerBuilder::new(directives).init().unwrap();
370 }
371
372 fn run_test_in_another_process(
373 test_name: &str,
374 test_body: impl FnOnce(),
375 ) -> Option<std::process::Output> {
376 if env::var("RUN_FORKED_TEST").is_ok() {
377 test_body();
378 None
379 } else {
380 let output = Command::new(env::current_exe().unwrap())
381 .arg(test_name)
382 .env("RUN_FORKED_TEST", "1")
383 .output()
384 .unwrap();
385
386 assert!(output.status.success());
387 Some(output)
388 }
389 }
390
391 #[test]
392 fn test_logger_filters() {
393 run_test_in_another_process("test_logger_filters", || {
394 let test_directives =
395 "grandpa=debug,sync=trace,client=warn,telemetry,something-with-dash=error";
396 init_logger(&test_directives);
397
398 tracing::dispatcher::get_default(|dispatcher| {
399 let test_filter = |target, level| {
400 struct DummyCallSite;
401 impl Callsite for DummyCallSite {
402 fn set_interest(&self, _: Interest) {}
403 fn metadata(&self) -> &Metadata<'_> {
404 unreachable!();
405 }
406 }
407
408 let metadata = tracing::metadata!(
409 name: "",
410 target: target,
411 level: level,
412 fields: &[],
413 callsite: &DummyCallSite,
414 kind: Kind::SPAN,
415 );
416
417 dispatcher.enabled(&metadata)
418 };
419
420 assert!(test_filter("grandpa", Level::INFO));
421 assert!(test_filter("grandpa", Level::DEBUG));
422 assert!(!test_filter("grandpa", Level::TRACE));
423
424 assert!(test_filter("sync", Level::TRACE));
425 assert!(test_filter("client", Level::WARN));
426
427 assert!(test_filter("telemetry", Level::TRACE));
428 assert!(test_filter("something-with-dash", Level::ERROR));
429 });
430 });
431 }
432
433 #[test]
436 fn dash_in_target_name_works() {
437 let executable = env::current_exe().unwrap();
438 let output = Command::new(executable)
439 .env("ENABLE_LOGGING", "1")
440 .args(&["--nocapture", "log_something_with_dash_target_name"])
441 .output()
442 .unwrap();
443
444 let output = String::from_utf8(output.stderr).unwrap();
445 assert!(output.contains(EXPECTED_LOG_MESSAGE));
446 }
447
448 #[test]
453 fn log_something_with_dash_target_name() {
454 if env::var("ENABLE_LOGGING").is_ok() {
455 let test_directives = "test-target=info";
456 let _guard = init_logger(&test_directives);
457
458 log::info!(target: "test-target", "{}", EXPECTED_LOG_MESSAGE);
459 }
460 }
461
462 #[test]
463 fn prefix_in_log_lines() {
464 let re = regex::Regex::new(&format!(
465 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} \[{}\] {}$",
466 EXPECTED_NODE_NAME, EXPECTED_LOG_MESSAGE,
467 ))
468 .unwrap();
469 let executable = env::current_exe().unwrap();
470 let output = Command::new(executable)
471 .env("ENABLE_LOGGING", "1")
472 .args(&["--nocapture", "prefix_in_log_lines_entrypoint"])
473 .output()
474 .unwrap();
475
476 let output = String::from_utf8(output.stderr).unwrap();
477 assert!(re.is_match(output.trim()), "Expected:\n{}\nGot:\n{}", re, output);
478 }
479
480 #[test]
484 fn prefix_in_log_lines_entrypoint() {
485 if env::var("ENABLE_LOGGING").is_ok() {
486 let _guard = init_logger("");
487 prefix_in_log_lines_process();
488 }
489 }
490
491 #[crate::logging::prefix_logs_with(EXPECTED_NODE_NAME)]
492 fn prefix_in_log_lines_process() {
493 log::info!("{}", EXPECTED_LOG_MESSAGE);
494 }
495
496 #[test]
500 fn do_not_write_with_colors_on_tty_entrypoint() {
501 if env::var("ENABLE_LOGGING").is_ok() {
502 let _guard = init_logger("");
503 log::info!("{}", console::style(EXPECTED_LOG_MESSAGE).yellow());
504 }
505 }
506
507 #[test]
508 fn do_not_write_with_colors_on_tty() {
509 let re = regex::Regex::new(&format!(
510 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} {}$",
511 EXPECTED_LOG_MESSAGE,
512 ))
513 .unwrap();
514 let executable = env::current_exe().unwrap();
515 let output = Command::new(executable)
516 .env("ENABLE_LOGGING", "1")
517 .args(&["--nocapture", "do_not_write_with_colors_on_tty_entrypoint"])
518 .output()
519 .unwrap();
520
521 let output = String::from_utf8(output.stderr).unwrap();
522 assert!(re.is_match(output.trim()), "Expected:\n{}\nGot:\n{}", re, output);
523 }
524
525 #[test]
526 fn log_max_level_is_set_properly() {
527 fn run_test(rust_log: Option<String>, tracing_targets: Option<String>) -> String {
528 let executable = env::current_exe().unwrap();
529 let mut command = Command::new(executable);
530
531 command
532 .env("PRINT_MAX_LOG_LEVEL", "1")
533 .args(&["--nocapture", "log_max_level_is_set_properly"]);
534
535 if let Some(rust_log) = rust_log {
536 command.env("RUST_LOG", rust_log);
537 }
538
539 if let Some(tracing_targets) = tracing_targets {
540 command.env("TRACING_TARGETS", tracing_targets);
541 }
542
543 let output = command.output().unwrap();
544
545 dbg!(String::from_utf8(output.stderr)).unwrap()
546 }
547
548 if env::var("PRINT_MAX_LOG_LEVEL").is_ok() {
549 let mut builder = LoggerBuilder::new("");
550
551 if let Ok(targets) = env::var("TRACING_TARGETS") {
552 builder.with_profiling(crate::TracingReceiver::Log, targets);
553 }
554
555 builder.init().unwrap();
556
557 eprint!("MAX_LOG_LEVEL={:?}", log::max_level());
558 } else {
559 assert_eq!("MAX_LOG_LEVEL=Info", run_test(None, None));
560 assert_eq!("MAX_LOG_LEVEL=Trace", run_test(Some("test=trace".into()), None));
561 assert_eq!("MAX_LOG_LEVEL=Debug", run_test(Some("test=debug".into()), None));
562 assert_eq!("MAX_LOG_LEVEL=Trace", run_test(None, Some("test=info".into())));
563 }
564 }
565
566 fn run_on_many_threads(thread_count: usize, callback: impl Fn(usize) + 'static + Send + Clone) {
569 let started_count = Arc::new(AtomicUsize::new(0));
570 let barrier = Arc::new(AtomicBool::new(false));
571 let threads: Vec<_> = (0..thread_count)
572 .map(|nth_thread| {
573 let started_count = started_count.clone();
574 let barrier = barrier.clone();
575 let callback = callback.clone();
576
577 std::thread::spawn(move || {
578 started_count.fetch_add(1, Ordering::SeqCst);
579 while !barrier.load(Ordering::SeqCst) {
580 std::thread::yield_now();
581 }
582
583 callback(nth_thread);
584 })
585 })
586 .collect();
587
588 while started_count.load(Ordering::SeqCst) != thread_count {
589 std::thread::yield_now();
590 }
591 barrier.store(true, Ordering::SeqCst);
592
593 for thread in threads {
594 if let Err(error) = thread.join() {
595 println!("error: failed to join thread: {:?}", error);
596 unsafe { libc::abort() }
597 }
598 }
599 }
600
601 #[test]
602 fn parallel_logs_from_multiple_threads_are_properly_gathered() {
603 const THREAD_COUNT: usize = 128;
604 const LOGS_PER_THREAD: usize = 1024;
605
606 let output = run_test_in_another_process(
607 "parallel_logs_from_multiple_threads_are_properly_gathered",
608 || {
609 let builder = LoggerBuilder::new("");
610 builder.init().unwrap();
611
612 run_on_many_threads(THREAD_COUNT, |nth_thread| {
613 for _ in 0..LOGS_PER_THREAD {
614 info!("Thread <<{}>>", nth_thread);
615 }
616 });
617 },
618 );
619
620 if let Some(output) = output {
621 let stderr = String::from_utf8(output.stderr).unwrap();
622 let mut count_per_thread = BTreeMap::new();
623 for line in stderr.split("\n") {
624 if let Some(index_s) = line.find("Thread <<") {
625 let index_s = index_s + "Thread <<".len();
626 let index_e = line.find(">>").unwrap();
627 let nth_thread: usize = line[index_s..index_e].parse().unwrap();
628 *count_per_thread.entry(nth_thread).or_insert(0) += 1;
629 }
630 }
631
632 assert_eq!(count_per_thread.len(), THREAD_COUNT);
633 for (_, count) in count_per_thread {
634 assert_eq!(count, LOGS_PER_THREAD);
635 }
636 }
637 }
638
639 #[test]
640 fn huge_single_line_log_is_properly_printed_out() {
641 let mut line = String::new();
642 line.push_str("$$START$$");
643 for n in 0..16 * 1024 * 1024 {
644 let ch = b'a' + (n as u8 % (b'z' - b'a'));
645 line.push(char::from(ch));
646 }
647 line.push_str("$$END$$");
648
649 let output =
650 run_test_in_another_process("huge_single_line_log_is_properly_printed_out", || {
651 let builder = LoggerBuilder::new("");
652 builder.init().unwrap();
653 info!("{}", line);
654 });
655
656 if let Some(output) = output {
657 let stderr = String::from_utf8(output.stderr).unwrap();
658 assert!(stderr.contains(&line));
659 }
660 }
661}