1use std::{
7 env,
8 fs::{File, OpenOptions},
9 path::Path,
10 sync::Arc,
11};
12
13use is_terminal::IsTerminal as _;
14use tracing::Subscriber;
15use tracing_subscriber::{
16 fmt::{
17 self,
18 format::{FmtSpan, Format, Full},
19 time::FormatTime,
20 FormatFields, MakeWriter,
21 },
22 layer::{Layer, SubscriberExt as _},
23 registry::LookupSpan,
24 util::SubscriberInitExt,
25};
26
27pub fn init(log_name: &str) {
37 let env_filter = tracing_subscriber::EnvFilter::builder()
38 .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
39 .from_env_lossy();
40
41 let span_events = std::env::var("RUST_LOG_SPAN_EVENTS")
42 .ok()
43 .map(|s| fmt_span_from_str(&s))
44 .unwrap_or(FmtSpan::NONE);
45
46 let format = std::env::var("RUST_LOG_FORMAT").ok();
47
48 let color_output =
49 !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal();
50
51 let stderr_layer = prepare_formatted_layer(
52 format.as_deref(),
53 fmt::layer()
54 .with_span_events(span_events.clone())
55 .with_writer(std::io::stderr)
56 .with_ansi(color_output),
57 );
58
59 let maybe_log_file_layer = open_log_file(log_name).map(|file_writer| {
60 prepare_formatted_layer(
61 format.as_deref(),
62 fmt::layer()
63 .with_span_events(span_events)
64 .with_writer(Arc::new(file_writer))
65 .with_ansi(false),
66 )
67 });
68
69 tracing_subscriber::registry()
70 .with(env_filter)
71 .with(maybe_log_file_layer)
72 .with(stderr_layer)
73 .init();
74}
75
76fn open_log_file(log_name: &str) -> Option<File> {
83 let log_directory = env::var_os("LINERA_LOG_DIR")?;
84 let mut log_file_path = Path::new(&log_directory).join(log_name);
85 log_file_path.set_extension("log");
86
87 Some(
88 OpenOptions::new()
89 .append(true)
90 .create(true)
91 .open(log_file_path)
92 .expect("Failed to open log file for writing"),
93 )
94}
95
96fn prepare_formatted_layer<S, N, W, T>(
100 formatting: Option<&str>,
101 layer: fmt::Layer<S, N, Format<Full, T>, W>,
102) -> Box<dyn Layer<S> + Send + Sync>
103where
104 S: Subscriber + for<'span> LookupSpan<'span>,
105 N: for<'writer> FormatFields<'writer> + Send + Sync + 'static,
106 W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
107 T: FormatTime + Send + Sync + 'static,
108{
109 match formatting.unwrap_or("plain") {
110 "json" => layer.json().boxed(),
111 "pretty" => layer.pretty().boxed(),
112 "plain" => layer.boxed(),
113 format => {
114 panic!("Invalid RUST_LOG_FORMAT: `{format}`. Valid values are `json` or `pretty`.")
115 }
116 }
117}
118
119fn fmt_span_from_str(events: &str) -> FmtSpan {
120 let mut fmt_span = FmtSpan::NONE;
121 for event in events.split(',') {
122 fmt_span |= match event {
123 "new" => FmtSpan::NEW,
124 "enter" => FmtSpan::ENTER,
125 "exit" => FmtSpan::EXIT,
126 "close" => FmtSpan::CLOSE,
127 "active" => FmtSpan::ACTIVE,
128 "full" => FmtSpan::FULL,
129 _ => FmtSpan::NONE,
130 };
131 }
132 fmt_span
133}