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 EnvFilter,
26};
27
28#[cfg(not(target_arch = "wasm32"))]
29pub use crate::tracing_opentelemetry::{
30 init_with_chrome_trace_exporter, init_with_opentelemetry, ChromeTraceGuard,
31};
32
33pub(crate) struct EnvConfig {
34 pub(crate) env_filter: EnvFilter,
35 span_events: FmtSpan,
36 format: Option<String>,
37 color_output: bool,
38 log_name: String,
39}
40
41impl EnvConfig {
42 pub(crate) fn stderr_layer<S>(&self) -> Box<dyn Layer<S> + Send + Sync>
43 where
44 S: Subscriber + for<'span> LookupSpan<'span>,
45 {
46 prepare_formatted_layer(
47 self.format.as_deref(),
48 fmt::layer()
49 .with_span_events(self.span_events.clone())
50 .with_writer(std::io::stderr)
51 .with_ansi(self.color_output),
52 )
53 }
54
55 pub(crate) fn maybe_log_file_layer<S>(&self) -> Option<Box<dyn Layer<S> + Send + Sync>>
56 where
57 S: Subscriber + for<'span> LookupSpan<'span>,
58 {
59 open_log_file(&self.log_name).map(|file_writer| {
60 prepare_formatted_layer(
61 self.format.as_deref(),
62 fmt::layer()
63 .with_span_events(self.span_events.clone())
64 .with_writer(Arc::new(file_writer))
65 .with_ansi(false),
66 )
67 })
68 }
69}
70
71pub fn init(log_name: &str) {
81 let config = get_env_config(log_name);
82 let maybe_log_file_layer = config.maybe_log_file_layer();
83 let stderr_layer = config.stderr_layer();
84
85 tracing_subscriber::registry()
86 .with(config.env_filter)
87 .with(maybe_log_file_layer)
88 .with(stderr_layer)
89 .init();
90}
91
92pub(crate) fn get_env_config(log_name: &str) -> EnvConfig {
93 let env_filter = EnvFilter::builder()
94 .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
95 .from_env_lossy();
96
97 let span_events = std::env::var("RUST_LOG_SPAN_EVENTS")
98 .ok()
99 .map_or(FmtSpan::NONE, |s| fmt_span_from_str(&s));
100
101 let format = std::env::var("RUST_LOG_FORMAT").ok();
102 let color_output =
103 !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal();
104
105 EnvConfig {
106 env_filter,
107 span_events,
108 format,
109 color_output,
110 log_name: log_name.to_string(),
111 }
112}
113
114pub(crate) fn open_log_file(log_name: &str) -> Option<File> {
121 let log_directory = env::var_os("LINERA_LOG_DIR")?;
122 let mut log_file_path = Path::new(&log_directory).join(log_name);
123 log_file_path.set_extension("log");
124
125 Some(
126 OpenOptions::new()
127 .append(true)
128 .create(true)
129 .open(log_file_path)
130 .expect("Failed to open log file for writing"),
131 )
132}
133
134pub(crate) fn prepare_formatted_layer<S, N, W, T>(
138 formatting: Option<&str>,
139 layer: fmt::Layer<S, N, Format<Full, T>, W>,
140) -> Box<dyn Layer<S> + Send + Sync>
141where
142 S: Subscriber + for<'span> LookupSpan<'span>,
143 N: for<'writer> FormatFields<'writer> + Send + Sync + 'static,
144 W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
145 T: FormatTime + Send + Sync + 'static,
146{
147 match formatting.unwrap_or("plain") {
148 "json" => layer.json().boxed(),
149 "pretty" => layer.pretty().boxed(),
150 "plain" => layer.boxed(),
151 format => {
152 panic!("Invalid RUST_LOG_FORMAT: `{format}`. Valid values are `json` or `pretty`.")
153 }
154 }
155}
156
157pub(crate) fn fmt_span_from_str(events: &str) -> FmtSpan {
158 let mut fmt_span = FmtSpan::NONE;
159 for event in events.split(',') {
160 fmt_span |= match event {
161 "new" => FmtSpan::NEW,
162 "enter" => FmtSpan::ENTER,
163 "exit" => FmtSpan::EXIT,
164 "close" => FmtSpan::CLOSE,
165 "active" => FmtSpan::ACTIVE,
166 "full" => FmtSpan::FULL,
167 _ => FmtSpan::NONE,
168 };
169 }
170 fmt_span
171}