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