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()
178 .with_interest_cache(tracing_log::InterestCacheConfig::default())
179 .with_max_level(max_level)
180 .init()?;
181
182 let detailed_output = match max_level_hint {
184 Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => false,
185 _ => true,
186 } || detailed_output;
187
188 let enable_color = force_colors.unwrap_or_else(|| io::stderr().is_terminal());
189 let timer = fast_local_time::FastLocalTime { with_fractional: detailed_output };
190
191 console::set_colors_enabled(enable_color);
193 console::set_colors_enabled_stderr(enable_color);
194
195 let event_format = EventFormat {
196 timer,
197 display_target: detailed_output,
198 display_level: detailed_output,
199 display_thread_name: detailed_output,
200 dup_to_stdout: !io::stderr().is_terminal() && io::stdout().is_terminal(),
201 };
202 let builder = FmtSubscriber::builder().with_env_filter(env_filter);
203
204 let builder = builder.with_span_events(format::FmtSpan::NONE);
205
206 let builder = builder.with_writer(MakeStderrWriter::default());
207
208 let builder = builder.event_format(event_format);
209
210 let builder = builder_hook(builder);
211
212 let subscriber = builder.finish().with(PrefixLayer);
213
214 Ok(subscriber)
215}
216
217pub struct LoggerBuilder {
219 directives: String,
220 profiling: Option<(crate::TracingReceiver, String)>,
221 custom_profiler: Option<Box<dyn crate::TraceHandler>>,
222 log_reloading: bool,
223 force_colors: Option<bool>,
224 detailed_output: bool,
225}
226
227impl LoggerBuilder {
228 pub fn new<S: Into<String>>(directives: S) -> Self {
230 Self {
231 directives: directives.into(),
232 profiling: None,
233 custom_profiler: None,
234 log_reloading: false,
235 force_colors: None,
236 detailed_output: false,
237 }
238 }
239
240 pub fn with_profiling<S: Into<String>>(
242 &mut self,
243 tracing_receiver: crate::TracingReceiver,
244 profiling_targets: S,
245 ) -> &mut Self {
246 self.profiling = Some((tracing_receiver, profiling_targets.into()));
247 self
248 }
249
250 pub fn with_custom_profiling(
252 &mut self,
253 custom_profiler: Box<dyn crate::TraceHandler>,
254 ) -> &mut Self {
255 self.custom_profiler = Some(custom_profiler);
256 self
257 }
258
259 pub fn with_log_reloading(&mut self, enabled: bool) -> &mut Self {
261 self.log_reloading = enabled;
262 self
263 }
264
265 pub fn with_detailed_output(&mut self, detailed: bool) -> &mut Self {
272 self.detailed_output = detailed;
273 self
274 }
275
276 pub fn with_colors(&mut self, enable: bool) -> &mut Self {
278 self.force_colors = Some(enable);
279 self
280 }
281
282 pub fn init(self) -> Result<()> {
286 if let Some((tracing_receiver, profiling_targets)) = self.profiling {
287 if self.log_reloading {
288 let subscriber = prepare_subscriber(
289 &self.directives,
290 Some(&profiling_targets),
291 self.force_colors,
292 self.detailed_output,
293 |builder| enable_log_reloading!(builder),
294 )?;
295 let mut profiling =
296 crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
297
298 self.custom_profiler
299 .into_iter()
300 .for_each(|profiler| profiling.add_handler(profiler));
301
302 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
303
304 Ok(())
305 } else {
306 let subscriber = prepare_subscriber(
307 &self.directives,
308 Some(&profiling_targets),
309 self.force_colors,
310 self.detailed_output,
311 |builder| builder,
312 )?;
313 let mut profiling =
314 crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
315
316 self.custom_profiler
317 .into_iter()
318 .for_each(|profiler| profiling.add_handler(profiler));
319
320 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
321
322 Ok(())
323 }
324 } else if self.log_reloading {
325 let subscriber = prepare_subscriber(
326 &self.directives,
327 None,
328 self.force_colors,
329 self.detailed_output,
330 |builder| enable_log_reloading!(builder),
331 )?;
332
333 tracing::subscriber::set_global_default(subscriber)?;
334
335 Ok(())
336 } else {
337 let subscriber = prepare_subscriber(
338 &self.directives,
339 None,
340 self.force_colors,
341 self.detailed_output,
342 |builder| builder,
343 )?;
344
345 tracing::subscriber::set_global_default(subscriber)?;
346
347 Ok(())
348 }
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate as sc_tracing;
356 use log::info;
357 use std::{
358 collections::BTreeMap,
359 env,
360 process::Command,
361 sync::{
362 atomic::{AtomicBool, AtomicUsize, Ordering},
363 Arc,
364 },
365 };
366 use tracing::{metadata::Kind, subscriber::Interest, Callsite, Level, Metadata};
367
368 const EXPECTED_LOG_MESSAGE: &'static str = "yeah logging works as expected";
369 const EXPECTED_NODE_NAME: &'static str = "THE_NODE";
370
371 fn init_logger(directives: &str) {
372 let _ = LoggerBuilder::new(directives).init().unwrap();
373 }
374
375 fn run_test_in_another_process(
376 test_name: &str,
377 test_body: impl FnOnce(),
378 ) -> Option<std::process::Output> {
379 if env::var("RUN_FORKED_TEST").is_ok() {
380 test_body();
381 None
382 } else {
383 let output = Command::new(env::current_exe().unwrap())
384 .arg(test_name)
385 .env("RUN_FORKED_TEST", "1")
386 .output()
387 .unwrap();
388
389 assert!(output.status.success());
390 Some(output)
391 }
392 }
393
394 #[test]
395 fn test_logger_filters() {
396 run_test_in_another_process("test_logger_filters", || {
397 let test_directives =
398 "grandpa=debug,sync=trace,client=warn,telemetry,something-with-dash=error";
399 init_logger(&test_directives);
400
401 tracing::dispatcher::get_default(|dispatcher| {
402 let test_filter = |target, level| {
403 struct DummyCallSite;
404 impl Callsite for DummyCallSite {
405 fn set_interest(&self, _: Interest) {}
406 fn metadata(&self) -> &Metadata<'_> {
407 unreachable!();
408 }
409 }
410
411 let metadata = tracing::metadata!(
412 name: "",
413 target: target,
414 level: level,
415 fields: &[],
416 callsite: &DummyCallSite,
417 kind: Kind::SPAN,
418 );
419
420 dispatcher.enabled(&metadata)
421 };
422
423 assert!(test_filter("grandpa", Level::INFO));
424 assert!(test_filter("grandpa", Level::DEBUG));
425 assert!(!test_filter("grandpa", Level::TRACE));
426
427 assert!(test_filter("sync", Level::TRACE));
428 assert!(test_filter("client", Level::WARN));
429
430 assert!(test_filter("telemetry", Level::TRACE));
431 assert!(test_filter("something-with-dash", Level::ERROR));
432 });
433 });
434 }
435
436 #[test]
439 fn dash_in_target_name_works() {
440 let executable = env::current_exe().unwrap();
441 let output = Command::new(executable)
442 .env("ENABLE_LOGGING", "1")
443 .args(&["--nocapture", "log_something_with_dash_target_name"])
444 .output()
445 .unwrap();
446
447 let output = String::from_utf8(output.stderr).unwrap();
448 assert!(output.contains(EXPECTED_LOG_MESSAGE));
449 }
450
451 #[test]
456 fn log_something_with_dash_target_name() {
457 if env::var("ENABLE_LOGGING").is_ok() {
458 let test_directives = "test-target=info";
459 let _guard = init_logger(&test_directives);
460
461 log::info!(target: "test-target", "{}", EXPECTED_LOG_MESSAGE);
462 }
463 }
464
465 #[test]
466 fn prefix_in_log_lines() {
467 let re = regex::Regex::new(&format!(
468 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} \[{}\] {}$",
469 EXPECTED_NODE_NAME, EXPECTED_LOG_MESSAGE,
470 ))
471 .unwrap();
472 let executable = env::current_exe().unwrap();
473 let output = Command::new(executable)
474 .env("ENABLE_LOGGING", "1")
475 .args(&["--nocapture", "prefix_in_log_lines_entrypoint"])
476 .output()
477 .unwrap();
478
479 let output = String::from_utf8(output.stderr).unwrap();
480 assert!(re.is_match(output.trim()), "Expected:\n{}\nGot:\n{}", re, output);
481 }
482
483 #[test]
487 fn prefix_in_log_lines_entrypoint() {
488 if env::var("ENABLE_LOGGING").is_ok() {
489 let _guard = init_logger("");
490 prefix_in_log_lines_process();
491 }
492 }
493
494 #[crate::logging::prefix_logs_with(EXPECTED_NODE_NAME)]
495 fn prefix_in_log_lines_process() {
496 log::info!("{}", EXPECTED_LOG_MESSAGE);
497 }
498
499 #[test]
503 fn do_not_write_with_colors_on_tty_entrypoint() {
504 if env::var("ENABLE_LOGGING").is_ok() {
505 let _guard = init_logger("");
506 log::info!("{}", console::style(EXPECTED_LOG_MESSAGE).yellow());
507 }
508 }
509
510 #[test]
511 fn do_not_write_with_colors_on_tty() {
512 let re = regex::Regex::new(&format!(
513 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} {}$",
514 EXPECTED_LOG_MESSAGE,
515 ))
516 .unwrap();
517 let executable = env::current_exe().unwrap();
518 let output = Command::new(executable)
519 .env("ENABLE_LOGGING", "1")
520 .args(&["--nocapture", "do_not_write_with_colors_on_tty_entrypoint"])
521 .output()
522 .unwrap();
523
524 let output = String::from_utf8(output.stderr).unwrap();
525 assert!(re.is_match(output.trim()), "Expected:\n{}\nGot:\n{}", re, output);
526 }
527
528 #[test]
529 fn log_max_level_is_set_properly() {
530 fn run_test(rust_log: Option<String>, tracing_targets: Option<String>) -> String {
531 let executable = env::current_exe().unwrap();
532 let mut command = Command::new(executable);
533
534 command
535 .env("PRINT_MAX_LOG_LEVEL", "1")
536 .args(&["--nocapture", "log_max_level_is_set_properly"]);
537
538 if let Some(rust_log) = rust_log {
539 command.env("RUST_LOG", rust_log);
540 }
541
542 if let Some(tracing_targets) = tracing_targets {
543 command.env("TRACING_TARGETS", tracing_targets);
544 }
545
546 let output = command.output().unwrap();
547
548 dbg!(String::from_utf8(output.stderr)).unwrap()
549 }
550
551 if env::var("PRINT_MAX_LOG_LEVEL").is_ok() {
552 let mut builder = LoggerBuilder::new("");
553
554 if let Ok(targets) = env::var("TRACING_TARGETS") {
555 builder.with_profiling(crate::TracingReceiver::Log, targets);
556 }
557
558 builder.init().unwrap();
559
560 eprint!("MAX_LOG_LEVEL={:?}", log::max_level());
561 } else {
562 assert_eq!("MAX_LOG_LEVEL=Info", run_test(None, None));
563 assert_eq!("MAX_LOG_LEVEL=Trace", run_test(Some("test=trace".into()), None));
564 assert_eq!("MAX_LOG_LEVEL=Debug", run_test(Some("test=debug".into()), None));
565 assert_eq!("MAX_LOG_LEVEL=Trace", run_test(None, Some("test=info".into())));
566 }
567 }
568
569 fn run_on_many_threads(thread_count: usize, callback: impl Fn(usize) + 'static + Send + Clone) {
572 let started_count = Arc::new(AtomicUsize::new(0));
573 let barrier = Arc::new(AtomicBool::new(false));
574 let threads: Vec<_> = (0..thread_count)
575 .map(|nth_thread| {
576 let started_count = started_count.clone();
577 let barrier = barrier.clone();
578 let callback = callback.clone();
579
580 std::thread::spawn(move || {
581 started_count.fetch_add(1, Ordering::SeqCst);
582 while !barrier.load(Ordering::SeqCst) {
583 std::thread::yield_now();
584 }
585
586 callback(nth_thread);
587 })
588 })
589 .collect();
590
591 while started_count.load(Ordering::SeqCst) != thread_count {
592 std::thread::yield_now();
593 }
594 barrier.store(true, Ordering::SeqCst);
595
596 for thread in threads {
597 if let Err(error) = thread.join() {
598 println!("error: failed to join thread: {:?}", error);
599 unsafe { libc::abort() }
600 }
601 }
602 }
603
604 #[test]
605 fn parallel_logs_from_multiple_threads_are_properly_gathered() {
606 const THREAD_COUNT: usize = 128;
607 const LOGS_PER_THREAD: usize = 1024;
608
609 let output = run_test_in_another_process(
610 "parallel_logs_from_multiple_threads_are_properly_gathered",
611 || {
612 let builder = LoggerBuilder::new("");
613 builder.init().unwrap();
614
615 run_on_many_threads(THREAD_COUNT, |nth_thread| {
616 for _ in 0..LOGS_PER_THREAD {
617 info!("Thread <<{}>>", nth_thread);
618 }
619 });
620 },
621 );
622
623 if let Some(output) = output {
624 let stderr = String::from_utf8(output.stderr).unwrap();
625 let mut count_per_thread = BTreeMap::new();
626 for line in stderr.split("\n") {
627 if let Some(index_s) = line.find("Thread <<") {
628 let index_s = index_s + "Thread <<".len();
629 let index_e = line.find(">>").unwrap();
630 let nth_thread: usize = line[index_s..index_e].parse().unwrap();
631 *count_per_thread.entry(nth_thread).or_insert(0) += 1;
632 }
633 }
634
635 assert_eq!(count_per_thread.len(), THREAD_COUNT);
636 for (_, count) in count_per_thread {
637 assert_eq!(count, LOGS_PER_THREAD);
638 }
639 }
640 }
641
642 #[test]
643 fn huge_single_line_log_is_properly_printed_out() {
644 let mut line = String::new();
645 line.push_str("$$START$$");
646 for n in 0..16 * 1024 * 1024 {
647 let ch = b'a' + (n as u8 % (b'z' - b'a'));
648 line.push(char::from(ch));
649 }
650 line.push_str("$$END$$");
651
652 let output =
653 run_test_in_another_process("huge_single_line_log_is_properly_printed_out", || {
654 let builder = LoggerBuilder::new("");
655 builder.init().unwrap();
656 info!("{}", line);
657 });
658
659 if let Some(output) = output {
660 let stderr = String::from_utf8(output.stderr).unwrap();
661 assert!(stderr.contains(&line));
662 }
663 }
664}