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#[cfg(not(target_arch = "wasm32"))]
28use {
29 opentelemetry::trace::TraceContextExt as _, tracing_opentelemetry::OtelData,
30 tracing_subscriber::fmt::FormatEvent,
31};
32
33#[cfg(not(target_arch = "wasm32"))]
34pub use crate::tracing_opentelemetry::{
35 init_with_chrome_trace_exporter, init_with_opentelemetry, ChromeTraceGuard,
36};
37
38pub(crate) struct EnvConfig {
39 pub(crate) env_filter: EnvFilter,
40 span_events: FmtSpan,
41 format: Option<String>,
42 color_output: bool,
43 log_name: String,
44}
45
46impl EnvConfig {
47 pub(crate) fn stderr_layer<S>(&self) -> Box<dyn Layer<S> + Send + Sync>
48 where
49 S: Subscriber + for<'span> LookupSpan<'span>,
50 {
51 prepare_formatted_layer(
52 self.format.as_deref(),
53 fmt::layer()
54 .with_span_events(self.span_events.clone())
55 .with_writer(std::io::stderr)
56 .with_ansi(self.color_output),
57 )
58 }
59
60 pub(crate) fn maybe_log_file_layer<S>(&self) -> Option<Box<dyn Layer<S> + Send + Sync>>
61 where
62 S: Subscriber + for<'span> LookupSpan<'span>,
63 {
64 open_log_file(&self.log_name).map(|file_writer| {
65 prepare_formatted_layer(
66 self.format.as_deref(),
67 fmt::layer()
68 .with_span_events(self.span_events.clone())
69 .with_writer(Arc::new(file_writer))
70 .with_ansi(false),
71 )
72 })
73 }
74}
75
76pub fn init(log_name: &str) {
91 let config = get_env_config(log_name);
92 let maybe_log_file_layer = config.maybe_log_file_layer();
93 let stderr_layer = config.stderr_layer();
94
95 tracing_subscriber::registry()
96 .with(config.env_filter)
97 .with(maybe_log_file_layer)
98 .with(stderr_layer)
99 .init();
100
101 #[cfg(not(target_arch = "wasm32"))]
104 crate::panic_hook::init();
105}
106
107pub(crate) fn get_env_config(log_name: &str) -> EnvConfig {
108 let env_filter = EnvFilter::builder()
109 .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
110 .from_env_lossy();
111
112 let span_events = std::env::var("RUST_LOG_SPAN_EVENTS")
113 .ok()
114 .map_or(FmtSpan::NONE, |s| fmt_span_from_str(&s));
115
116 let format = std::env::var("RUST_LOG_FORMAT").ok();
117 let color_output =
118 !std::env::var("NO_COLOR").is_ok_and(|x| !x.is_empty()) && std::io::stderr().is_terminal();
119
120 EnvConfig {
121 env_filter,
122 span_events,
123 format,
124 color_output,
125 log_name: log_name.to_string(),
126 }
127}
128
129pub(crate) fn open_log_file(log_name: &str) -> Option<File> {
136 let log_directory = env::var_os("LINERA_LOG_DIR")?;
137 let mut log_file_path = Path::new(&log_directory).join(log_name);
138 log_file_path.set_extension("log");
139
140 Some(
141 OpenOptions::new()
142 .append(true)
143 .create(true)
144 .open(log_file_path)
145 .expect("Failed to open log file for writing"),
146 )
147}
148
149#[cfg(not(target_arch = "wasm32"))]
150struct WithTraceContext;
151
152#[cfg(not(target_arch = "wasm32"))]
153impl<S, N> FormatEvent<S, N> for WithTraceContext
154where
155 S: Subscriber + for<'span> LookupSpan<'span>,
156 N: for<'writer> FormatFields<'writer> + 'static,
157{
158 fn format_event(
159 &self,
160 ctx: &fmt::FmtContext<'_, S, N>,
161 mut writer: fmt::format::Writer<'_>,
162 event: &tracing::Event<'_>,
163 ) -> std::fmt::Result {
164 if let Some(scope) = ctx.event_scope() {
165 for span in scope {
166 let extensions = span.extensions();
167 if let Some(otel_data) = extensions.get::<OtelData>() {
168 let trace_id = otel_data
171 .builder
172 .trace_id
173 .unwrap_or_else(|| otel_data.parent_cx.span().span_context().trace_id());
174 if trace_id != opentelemetry::trace::TraceId::INVALID {
175 write!(writer, "traceID={trace_id} ")?;
176 }
177 if let Some(span_id) = otel_data.builder.span_id {
178 write!(writer, "spanID={span_id} ")?;
179 }
180 break;
181 }
182 }
183 }
184 Format::default().format_event(ctx, writer, event)
185 }
186}
187
188pub(crate) fn prepare_formatted_layer<S, N, W, T>(
192 formatting: Option<&str>,
193 layer: fmt::Layer<S, N, Format<Full, T>, W>,
194) -> Box<dyn Layer<S> + Send + Sync>
195where
196 S: Subscriber + for<'span> LookupSpan<'span>,
197 N: for<'writer> FormatFields<'writer> + Send + Sync + 'static,
198 W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
199 T: FormatTime + Send + Sync + 'static,
200{
201 match formatting.unwrap_or("plain") {
202 "json" => layer.json().boxed(),
203 "pretty" => layer.pretty().boxed(),
204 "plain" => {
205 #[cfg(not(target_arch = "wasm32"))]
206 {
207 layer.event_format(WithTraceContext).boxed()
208 }
209 #[cfg(target_arch = "wasm32")]
210 {
211 layer.boxed()
212 }
213 }
214 format => {
215 panic!("Invalid RUST_LOG_FORMAT: `{format}`. Valid values are `json` or `pretty`.")
216 }
217 }
218}
219
220pub(crate) fn fmt_span_from_str(events: &str) -> FmtSpan {
221 let mut fmt_span = FmtSpan::NONE;
222 for event in events.split(',') {
223 fmt_span |= match event {
224 "new" => FmtSpan::NEW,
225 "enter" => FmtSpan::ENTER,
226 "exit" => FmtSpan::EXIT,
227 "close" => FmtSpan::CLOSE,
228 "active" => FmtSpan::ACTIVE,
229 "full" => FmtSpan::FULL,
230 _ => FmtSpan::NONE,
231 };
232 }
233 fmt_span
234}