1use std::{env, fmt, io, string::ToString};
4
5use ansi_term::{Color, Style};
6use anyhow::anyhow;
7use datasize::DataSize;
8use once_cell::sync::OnceCell;
9use serde::{Deserialize, Serialize};
10use smallvec::SmallVec;
11use tracing::{
12 field::{Field, Visit},
13 Event, Level, Subscriber,
14};
15use tracing_subscriber::{
16 fmt::{
17 format::{self, FieldFn, Format, Json, JsonFields, Writer},
18 time::{FormatTime, SystemTime},
19 FmtContext, FormatEvent, FormatFields, FormattedFields, Layer,
20 },
21 layer::Layered,
22 registry::LookupSpan,
23 reload::{self, Handle},
24 EnvFilter, Registry,
25};
26
27const LOG_VERBOSITY_LEVEL_ENVVAR: &str = "RUST_LOG";
28
29const LOG_FIELD_MESSAGE: &str = "message";
30const LOG_FIELD_TARGET: &str = "log.target";
31const LOG_FIELD_MODULE: &str = "log.module_path";
32const LOG_FIELD_FILE: &str = "log.file";
33const LOG_FIELD_LINE: &str = "log.line";
34
35static RELOAD_HANDLE: OnceCell<ReloadHandle> = OnceCell::new();
39
40#[derive(Clone, DataSize, Debug, Default, Deserialize, Serialize)]
42#[serde(deny_unknown_fields)]
43pub struct LoggingConfig {
44 pub format: LoggingFormat,
46
47 pub color: bool,
52
53 pub abbreviate_modules: bool,
58}
59
60impl LoggingConfig {
61 #[cfg(test)]
63 pub fn new(format: LoggingFormat, color: bool, abbreviate_modules: bool) -> Self {
64 LoggingConfig {
65 format,
66 color,
67 abbreviate_modules,
68 }
69 }
70}
71
72#[derive(Clone, DataSize, Debug, Deserialize, Serialize, Default)]
76#[serde(rename_all = "lowercase")]
77pub enum LoggingFormat {
78 #[default]
80 Text,
81 Json,
83}
84
85pub struct FmtEvent {
88 ansi_color: bool,
90 abbreviate_modules: bool,
92}
93
94impl FmtEvent {
95 fn new(ansi_color: bool, abbreviate_modules: bool) -> Self {
96 FmtEvent {
97 ansi_color,
98 abbreviate_modules,
99 }
100 }
101
102 fn enable_dimmed_if_ansi(&self, writer: &mut dyn fmt::Write) -> fmt::Result {
103 if self.ansi_color {
104 write!(writer, "{}", Style::new().dimmed().prefix())
105 } else {
106 Ok(())
107 }
108 }
109
110 fn disable_dimmed_if_ansi(&self, writer: &mut dyn fmt::Write) -> fmt::Result {
111 if self.ansi_color {
112 write!(writer, "{}", Style::new().dimmed().suffix())
113 } else {
114 Ok(())
115 }
116 }
117}
118
119#[derive(Default)]
122struct FieldVisitor {
123 module: Option<String>,
124 file: Option<String>,
125 line: Option<u32>,
126}
127
128impl Visit for FieldVisitor {
129 fn record_str(&mut self, field: &Field, value: &str) {
130 if field.name() == LOG_FIELD_MODULE {
131 self.module = Some(value.to_string())
132 } else if field.name() == LOG_FIELD_FILE {
133 self.file = Some(value.to_string())
134 }
135 }
136
137 fn record_u64(&mut self, field: &Field, value: u64) {
138 if field.name() == LOG_FIELD_LINE {
139 self.line = Some(value as u32)
140 }
141 }
142
143 fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {}
144}
145
146impl<S, N> FormatEvent<S, N> for FmtEvent
147where
148 S: Subscriber + for<'a> LookupSpan<'a>,
149 N: for<'a> FormatFields<'a> + 'static,
150{
151 fn format_event(
152 &self,
153 ctx: &FmtContext<'_, S, N>,
154 mut writer: Writer<'_>,
155 event: &Event<'_>,
156 ) -> fmt::Result {
157 self.enable_dimmed_if_ansi(&mut writer)?;
159 SystemTime.format_time(&mut writer)?;
160 self.disable_dimmed_if_ansi(&mut writer)?;
161
162 let meta = event.metadata();
164 if self.ansi_color {
165 let color = match *meta.level() {
166 Level::TRACE => Color::Purple,
167 Level::DEBUG => Color::Blue,
168 Level::INFO => Color::Green,
169 Level::WARN => Color::Yellow,
170 Level::ERROR => Color::Red,
171 };
172
173 write!(
174 writer,
175 " {}{:<6}{}",
176 color.prefix(),
177 meta.level(),
178 color.suffix()
179 )?;
180 } else {
181 write!(writer, " {:<6}", meta.level().to_string())?;
182 }
183
184 let mut span_seen = false;
187
188 ctx.visit_spans(|span| {
189 write!(writer, "{}", span.metadata().name())?;
190 span_seen = true;
191
192 let ext = span.extensions();
193 let fields = &ext
194 .get::<FormattedFields<N>>()
195 .expect("Unable to find FormattedFields in extensions; this is a bug");
196 if !fields.is_empty() {
197 write!(writer, "{{{}}}", fields)?;
198 }
199 writer.write_char(':')
200 })?;
201
202 if span_seen {
203 writer.write_char(' ')?;
204 }
205
206 let mut field_visitor = FieldVisitor::default();
208 event.record(&mut field_visitor);
209 let module = {
210 let full_module_path = meta
211 .module_path()
212 .or(field_visitor.module.as_deref())
213 .unwrap_or_default();
214 if self.abbreviate_modules {
215 let mut parts: SmallVec<[&str; 6]> = full_module_path.split("::").collect();
217
218 let count = parts.len();
219 if count > 1 {
221 for part in parts.iter_mut().take(count - 1) {
222 assert!(part.is_ascii());
223 *part = &part[0..1];
224 }
225 }
226 parts.join(":")
228 } else {
229 full_module_path.to_owned()
230 }
231 };
232
233 let file = if !self.abbreviate_modules {
234 meta.file()
235 .or(field_visitor.file.as_deref())
236 .unwrap_or_default()
237 .rsplit_once('/')
238 .map(|parts| parts.1)
239 .unwrap_or_default()
240 } else {
241 ""
242 };
243
244 let line = meta.line().or(field_visitor.line).unwrap_or_default();
245
246 if !module.is_empty() && (!file.is_empty() || self.abbreviate_modules) {
247 self.enable_dimmed_if_ansi(&mut writer)?;
248 write!(writer, "[{} {}:{}] ", module, file, line,)?;
249 self.disable_dimmed_if_ansi(&mut writer)?;
250 }
251
252 ctx.format_fields(writer.by_ref(), event)?;
254 writeln!(writer)
255 }
256}
257
258#[cfg(test)]
262pub fn init() -> anyhow::Result<()> {
263 init_with_config(&Default::default())
264}
265
266#[allow(clippy::type_complexity)] pub enum ReloadHandle {
269 Text(Handle<EnvFilter, Layered<Layer<Registry, FieldFn<FormatDebugFn>, FmtEvent>, Registry>>),
271 Json(Handle<EnvFilter, Layered<Layer<Registry, JsonFields, Format<Json>>, Registry>>),
273}
274
275impl ReloadHandle {
276 fn reload_env_filter(&self, new_filter: EnvFilter) -> Result<(), reload::Error> {
278 match self {
279 ReloadHandle::Text(handle) => handle.reload(new_filter),
280 ReloadHandle::Json(handle) => handle.reload(new_filter),
281 }
282 }
283
284 fn display_log_filter(&self) -> Result<String, reload::Error> {
286 match self {
287 ReloadHandle::Text(handle) => handle.with_current(ToString::to_string),
288 ReloadHandle::Json(handle) => handle.with_current(ToString::to_string),
289 }
290 }
291}
292
293pub fn reload_global_env_filter(new_filter: EnvFilter) -> anyhow::Result<()> {
295 let handle = RELOAD_HANDLE
296 .get()
297 .ok_or_else(|| anyhow!("could not fetch reload handle - logger not initialized?"))?;
298 handle.reload_env_filter(new_filter)?;
299
300 Ok(())
301}
302
303pub fn display_global_env_filter() -> anyhow::Result<String> {
305 let handle = RELOAD_HANDLE
306 .get()
307 .ok_or_else(|| anyhow!("could not fetch reload handle - logger not initialized?"))?;
308 let formatted = handle.display_log_filter()?;
309
310 Ok(formatted)
311}
312
313pub type FormatDebugFn = fn(&mut Writer, &Field, &dyn fmt::Debug) -> fmt::Result;
315
316fn format_into_debug_writer(
317 writer: &mut Writer,
318 field: &Field,
319 value: &dyn fmt::Debug,
320) -> fmt::Result {
321 match field.name() {
322 LOG_FIELD_MESSAGE => write!(writer, "{:?}", value),
323 LOG_FIELD_TARGET | LOG_FIELD_MODULE | LOG_FIELD_FILE | LOG_FIELD_LINE => Ok(()),
324 _ => write!(writer, "; {}={:?}", field, value),
325 }
326}
327
328#[allow(trivial_casts)]
336pub fn init_with_config(config: &LoggingConfig) -> anyhow::Result<()> {
337 let formatter = format::debug_fn(format_into_debug_writer as FormatDebugFn);
338
339 let filter = EnvFilter::new(
340 env::var(LOG_VERBOSITY_LEVEL_ENVVAR)
341 .as_deref()
342 .unwrap_or("warn,casper_node=info"),
343 );
344
345 match config.format {
346 LoggingFormat::Text => {
348 let builder = tracing_subscriber::fmt()
349 .with_writer(io::stdout as fn() -> io::Stdout)
350 .with_env_filter(filter)
351 .fmt_fields(formatter)
352 .event_format(FmtEvent::new(config.color, config.abbreviate_modules))
353 .with_filter_reloading();
354 let handle = ReloadHandle::Text(builder.reload_handle());
355 builder.try_init().map_err(|error| anyhow!(error))?;
356 drop(RELOAD_HANDLE.set(handle));
357 Ok(())
358 }
359
360 LoggingFormat::Json => {
362 let builder = tracing_subscriber::fmt()
363 .with_writer(io::stdout as fn() -> io::Stdout)
364 .with_env_filter(filter)
365 .json()
366 .with_filter_reloading();
367 let handle = ReloadHandle::Json(builder.reload_handle());
368 builder.try_init().map_err(|error| anyhow!(error))?;
369 drop(RELOAD_HANDLE.set(handle));
370 Ok(())
371 }
372 }
373}