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