1#![warn(missing_docs)]
24
25mod directives;
26mod event_format;
27mod layers;
28
29pub use directives::*;
30pub use tc_tracing_proc_macro::*;
31
32use tc_telemetry::{ExtTransport, TelemetryWorker};
33use std::io;
34use tracing::Subscriber;
35use tracing_subscriber::{
36 fmt::time::ChronoLocal,
37 fmt::{
38 format, FormatEvent, FormatFields, Formatter, Layer as FmtLayer, MakeWriter,
39 SubscriberBuilder,
40 },
41 layer::{self, SubscriberExt},
42 registry::LookupSpan,
43 EnvFilter, FmtSubscriber, Layer, Registry,
44};
45
46pub use event_format::*;
47pub use layers::*;
48
49pub type Result<T> = std::result::Result<T, Error>;
51
52#[derive(Debug, thiserror::Error)]
54#[allow(missing_docs)]
55#[non_exhaustive]
56#[error(transparent)]
57pub enum Error {
58 IoError(#[from] io::Error),
59 SetGlobalDefaultError(#[from] tracing::subscriber::SetGlobalDefaultError),
60 DirectiveParseError(#[from] tracing_subscriber::filter::ParseError),
61 SetLoggerError(#[from] tracing_log::log_tracer::SetLoggerError),
62}
63
64macro_rules! enable_log_reloading {
65 ($builder:expr) => {{
66 let builder = $builder.with_filter_reloading();
67 let handle = builder.reload_handle();
68 set_reload_handle(handle);
69 builder
70 }};
71}
72
73fn prepare_subscriber<N, E, F, W>(
75 directives: &str,
76 max_level: Option<log::LevelFilter>,
77 force_colors: Option<bool>,
78 telemetry_buffer_size: Option<usize>,
79 telemetry_external_transport: Option<ExtTransport>,
80 builder_hook: impl Fn(
81 SubscriberBuilder<
82 format::DefaultFields,
83 EventFormat<ChronoLocal>,
84 EnvFilter,
85 fn() -> std::io::Stderr,
86 >,
87 ) -> SubscriberBuilder<N, E, F, W>,
88) -> Result<(impl Subscriber + for<'a> LookupSpan<'a>, TelemetryWorker)>
89where
90 N: for<'writer> FormatFields<'writer> + 'static,
91 E: FormatEvent<Registry, N> + 'static,
92 W: MakeWriter + 'static,
93 F: layer::Layer<Formatter<N, E, W>> + Send + Sync + 'static,
94 FmtLayer<Registry, N, E, W>: layer::Layer<Registry> + Send + Sync + 'static,
95{
96 fn parse_user_directives(mut env_filter: EnvFilter, dirs: &str) -> Result<EnvFilter> {
98 for dir in dirs.split(',') {
99 env_filter = env_filter.add_directive(parse_default_directive(&dir)?);
100 }
101 Ok(env_filter)
102 }
103
104 let mut env_filter = EnvFilter::default()
107 .add_directive(parse_default_directive("info").expect("provided directive is valid"))
109 .add_directive(parse_default_directive("ws=off").expect("provided directive is valid"))
111 .add_directive(parse_default_directive("remux=off").expect("provided directive is valid"))
112 .add_directive(
113 parse_default_directive("cranelift_codegen=off").expect("provided directive is valid"),
114 )
115 .add_directive(
117 parse_default_directive("cranelift_wasm=warn").expect("provided directive is valid"),
118 )
119 .add_directive(parse_default_directive("hyper=warn").expect("provided directive is valid"));
120
121 if let Ok(lvl) = std::env::var("RUST_LOG") {
122 if lvl != "" {
123 env_filter = parse_user_directives(env_filter, &lvl)?;
124 }
125 }
126
127 if directives != "" {
128 env_filter = parse_user_directives(env_filter, directives)?;
131 }
132
133 let max_level_hint = Layer::<FmtSubscriber>::max_level_hint(&env_filter);
134
135 let max_level = max_level.unwrap_or_else(|| match max_level_hint {
136 Some(tracing_subscriber::filter::LevelFilter::INFO) | None => log::LevelFilter::Info,
137 Some(tracing_subscriber::filter::LevelFilter::TRACE) => log::LevelFilter::Trace,
138 Some(tracing_subscriber::filter::LevelFilter::WARN) => log::LevelFilter::Warn,
139 Some(tracing_subscriber::filter::LevelFilter::ERROR) => log::LevelFilter::Error,
140 Some(tracing_subscriber::filter::LevelFilter::DEBUG) => log::LevelFilter::Debug,
141 Some(tracing_subscriber::filter::LevelFilter::OFF) => log::LevelFilter::Off,
142 });
143
144 tracing_log::LogTracer::builder()
145 .with_max_level(max_level)
146 .init()?;
147
148 let simple = match max_level_hint {
150 Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => true,
151 _ => false,
152 };
153
154 let enable_color = force_colors.unwrap_or_else(|| atty::is(atty::Stream::Stderr));
155 let timer = ChronoLocal::with_format(if simple {
156 "%Y-%m-%d %H:%M:%S".to_string()
157 } else {
158 "%Y-%m-%d %H:%M:%S%.3f".to_string()
159 });
160
161 let (telemetry_layer, telemetry_worker) =
162 tc_telemetry::TelemetryLayer::new(telemetry_buffer_size, telemetry_external_transport)?;
163 let event_format = EventFormat {
164 timer,
165 display_target: !simple,
166 display_level: !simple,
167 display_thread_name: !simple,
168 enable_color,
169 };
170 let builder = FmtSubscriber::builder().with_env_filter(env_filter);
171
172 #[cfg(not(target_os = "unknown"))]
173 let builder = builder.with_writer(std::io::stderr as _);
174
175 #[cfg(target_os = "unknown")]
176 let builder = builder.with_writer(std::io::sink);
177
178 #[cfg(not(target_os = "unknown"))]
179 let builder = builder.event_format(event_format);
180
181 #[cfg(not(target_os = "unknown"))]
182 let builder = builder_hook(builder);
183
184 let subscriber = builder.finish().with(PrefixLayer).with(telemetry_layer);
185
186 #[cfg(target_os = "unknown")]
187 let subscriber = subscriber.with(ConsoleLogLayer::new(event_format));
188
189 Ok((subscriber, telemetry_worker))
190}
191
192pub struct LoggerBuilder {
194 directives: String,
195 profiling: Option<(crate::TracingReceiver, String)>,
196 telemetry_buffer_size: Option<usize>,
197 telemetry_external_transport: Option<ExtTransport>,
198 log_reloading: bool,
199 force_colors: Option<bool>,
200}
201
202impl LoggerBuilder {
203 pub fn new<S: Into<String>>(directives: S) -> Self {
205 Self {
206 directives: directives.into(),
207 profiling: None,
208 telemetry_buffer_size: None,
209 telemetry_external_transport: None,
210 log_reloading: true,
211 force_colors: None,
212 }
213 }
214
215 pub fn with_profiling<S: Into<String>>(
217 &mut self,
218 tracing_receiver: crate::TracingReceiver,
219 profiling_targets: S,
220 ) -> &mut Self {
221 self.profiling = Some((tracing_receiver, profiling_targets.into()));
222 self
223 }
224
225 pub fn with_log_reloading(&mut self, enabled: bool) -> &mut Self {
227 self.log_reloading = enabled;
228 self
229 }
230
231 pub fn with_telemetry_buffer_size(&mut self, buffer_size: usize) -> &mut Self {
233 self.telemetry_buffer_size = Some(buffer_size);
234 self
235 }
236
237 pub fn with_transport(&mut self, transport: ExtTransport) -> &mut Self {
239 self.telemetry_external_transport = Some(transport);
240 self
241 }
242
243 pub fn with_colors(&mut self, enable: bool) -> &mut Self {
245 self.force_colors = Some(enable);
246 self
247 }
248
249 pub fn init(self) -> Result<TelemetryWorker> {
253 if let Some((tracing_receiver, profiling_targets)) = self.profiling {
254 let max_level = Some(log::LevelFilter::Trace);
256
257 if self.log_reloading {
258 let (subscriber, telemetry_worker) = prepare_subscriber(
259 &format!("{},{},tc_tracing=trace", self.directives, profiling_targets),
260 max_level,
261 self.force_colors,
262 self.telemetry_buffer_size,
263 self.telemetry_external_transport,
264 |builder| enable_log_reloading!(builder),
265 )?;
266 let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
267
268 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
269
270 Ok(telemetry_worker)
271 } else {
272 let (subscriber, telemetry_worker) = prepare_subscriber(
273 &format!("{},{},tc_tracing=trace", self.directives, profiling_targets),
274 max_level,
275 self.force_colors,
276 self.telemetry_buffer_size,
277 self.telemetry_external_transport,
278 |builder| builder,
279 )?;
280 let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
281
282 tracing::subscriber::set_global_default(subscriber.with(profiling))?;
283
284 Ok(telemetry_worker)
285 }
286 } else {
287 if self.log_reloading {
288 let (subscriber, telemetry_worker) = prepare_subscriber(
289 &self.directives,
290 None,
291 self.force_colors,
292 self.telemetry_buffer_size,
293 self.telemetry_external_transport,
294 |builder| enable_log_reloading!(builder),
295 )?;
296
297 tracing::subscriber::set_global_default(subscriber)?;
298
299 Ok(telemetry_worker)
300 } else {
301 let (subscriber, telemetry_worker) = prepare_subscriber(
302 &self.directives,
303 None,
304 self.force_colors,
305 self.telemetry_buffer_size,
306 self.telemetry_external_transport,
307 |builder| builder,
308 )?;
309
310 tracing::subscriber::set_global_default(subscriber)?;
311
312 Ok(telemetry_worker)
313 }
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate as tc_tracing;
322 use std::{env, process::Command};
323 use tracing::{metadata::Kind, subscriber::Interest, Callsite, Level, Metadata};
324
325 const EXPECTED_LOG_MESSAGE: &'static str = "yeah logging works as expected";
326 const EXPECTED_NODE_NAME: &'static str = "THE_NODE";
327
328 fn init_logger(directives: &str) {
329 let _ = LoggerBuilder::new(directives).init().unwrap();
330 }
331
332 fn run_in_process(test_name: &str) {
333 if env::var("RUN_IN_PROCESS").is_err() {
334 let status = Command::new(env::current_exe().unwrap())
335 .arg(test_name)
336 .env("RUN_IN_PROCESS", "true")
337 .status()
338 .unwrap();
339 assert!(status.success(), "process did not ended successfully");
340 std::process::exit(0);
341 }
342 }
343
344 #[test]
345 fn test_logger_filters() {
346 run_in_process("test_logger_filters");
347
348 let test_directives = "afg=debug,sync=trace,client=warn,telemetry,something-with-dash=error";
349 init_logger(&test_directives);
350
351 tracing::dispatcher::get_default(|dispatcher| {
352 let test_filter = |target, level| {
353 struct DummyCallSite;
354 impl Callsite for DummyCallSite {
355 fn set_interest(&self, _: Interest) {}
356 fn metadata(&self) -> &Metadata<'_> {
357 unreachable!();
358 }
359 }
360
361 let metadata = tracing::metadata!(
362 name: "",
363 target: target,
364 level: level,
365 fields: &[],
366 callsite: &DummyCallSite,
367 kind: Kind::SPAN,
368 );
369
370 dispatcher.enabled(&metadata)
371 };
372
373 assert!(test_filter("afg", Level::INFO));
374 assert!(test_filter("afg", Level::DEBUG));
375 assert!(!test_filter("afg", Level::TRACE));
376
377 assert!(test_filter("sync", Level::TRACE));
378 assert!(test_filter("client", Level::WARN));
379
380 assert!(test_filter("telemetry", Level::TRACE));
381 assert!(test_filter("something-with-dash", Level::ERROR));
382 });
383 }
384
385 #[test]
388 fn dash_in_target_name_works() {
389 let executable = env::current_exe().unwrap();
390 let output = Command::new(executable)
391 .env("ENABLE_LOGGING", "1")
392 .args(&["--nocapture", "log_something_with_dash_target_name"])
393 .output()
394 .unwrap();
395
396 let output = String::from_utf8(output.stderr).unwrap();
397 assert!(output.contains(EXPECTED_LOG_MESSAGE));
398 }
399
400 #[test]
405 fn log_something_with_dash_target_name() {
406 if env::var("ENABLE_LOGGING").is_ok() {
407 let test_directives = "test-target=info";
408 let _guard = init_logger(&test_directives);
409
410 log::info!(target: "test-target", "{}", EXPECTED_LOG_MESSAGE);
411 }
412 }
413
414 #[test]
415 fn prefix_in_log_lines() {
416 let re = regex::Regex::new(&format!(
417 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} \[{}\] {}$",
418 EXPECTED_NODE_NAME, EXPECTED_LOG_MESSAGE,
419 ))
420 .unwrap();
421 let executable = env::current_exe().unwrap();
422 let output = Command::new(executable)
423 .env("ENABLE_LOGGING", "1")
424 .args(&["--nocapture", "prefix_in_log_lines_entrypoint"])
425 .output()
426 .unwrap();
427
428 let output = String::from_utf8(output.stderr).unwrap();
429 assert!(
430 re.is_match(output.trim()),
431 format!("Expected:\n{}\nGot:\n{}", re, output),
432 );
433 }
434
435 #[test]
439 fn prefix_in_log_lines_entrypoint() {
440 if env::var("ENABLE_LOGGING").is_ok() {
441 let _guard = init_logger("");
442 prefix_in_log_lines_process();
443 }
444 }
445
446 #[crate::logging::prefix_logs_with(EXPECTED_NODE_NAME)]
447 fn prefix_in_log_lines_process() {
448 log::info!("{}", EXPECTED_LOG_MESSAGE);
449 }
450
451 #[test]
455 fn do_not_write_with_colors_on_tty_entrypoint() {
456 if env::var("ENABLE_LOGGING").is_ok() {
457 let _guard = init_logger("");
458 log::info!("{}", ansi_term::Colour::Yellow.paint(EXPECTED_LOG_MESSAGE));
459 }
460 }
461
462 #[test]
463 fn do_not_write_with_colors_on_tty() {
464 let re = regex::Regex::new(&format!(
465 r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} {}$",
466 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", "do_not_write_with_colors_on_tty_entrypoint"])
473 .output()
474 .unwrap();
475
476 let output = String::from_utf8(output.stderr).unwrap();
477 assert!(
478 re.is_match(output.trim()),
479 format!("Expected:\n{}\nGot:\n{}", re, output),
480 );
481 }
482
483 #[test]
484 fn log_max_level_is_set_properly() {
485 fn run_test(rust_log: Option<String>, tracing_targets: Option<String>) -> String {
486 let executable = env::current_exe().unwrap();
487 let mut command = Command::new(executable);
488
489 command
490 .env("PRINT_MAX_LOG_LEVEL", "1")
491 .args(&["--nocapture", "log_max_level_is_set_properly"]);
492
493 if let Some(rust_log) = rust_log {
494 command.env("RUST_LOG", rust_log);
495 }
496
497 if let Some(tracing_targets) = tracing_targets {
498 command.env("TRACING_TARGETS", tracing_targets);
499 }
500
501 let output = command.output().unwrap();
502
503 String::from_utf8(output.stderr).unwrap()
504 }
505
506 if env::var("PRINT_MAX_LOG_LEVEL").is_ok() {
507 init_logger(&env::var("TRACING_TARGETS").unwrap_or_default());
508 eprint!("MAX_LOG_LEVEL={:?}", log::max_level());
509 } else {
510 assert_eq!("MAX_LOG_LEVEL=Info", run_test(None, None));
511 assert_eq!(
512 "MAX_LOG_LEVEL=Trace",
513 run_test(Some("test=trace".into()), None)
514 );
515 assert_eq!(
516 "MAX_LOG_LEVEL=Debug",
517 run_test(Some("test=debug".into()), None)
518 );
519 assert_eq!(
520 "MAX_LOG_LEVEL=Trace",
521 run_test(None, Some("test=info".into()))
522 );
523 }
524 }
525}