trace_tools/subscriber/layer/
log.rs

1// Copyright 2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{subscriber::visitors::MessageVisitor, Error};
5
6use fern_logger::{LoggerConfig, LoggerOutputConfig};
7
8use colored::{ColoredString, Colorize};
9use parking_lot::{Mutex, MutexGuard};
10use tracing::{metadata::LevelFilter, Event, Level, Metadata, Subscriber};
11use tracing_log::{AsTrace, NormalizeEvent};
12use tracing_subscriber::{
13    filter::{self, Targets},
14    fmt::MakeWriter,
15    layer::{Context, Filter, Layer},
16    registry::LookupSpan,
17};
18
19use std::{
20    fs::{File, OpenOptions},
21    io::{self, Stdout, StdoutLock},
22};
23
24/// Describes the output target of a [`log`] event.
25///
26/// Variants wrap a locked writer to the output target.
27enum LogOutput<'a> {
28    /// Log to standard output, with optional color.
29    Stdout(StdoutLock<'a>, bool),
30    /// Log to a file.
31    File(MutexGuard<'a, File>),
32}
33
34impl<'a> io::Write for LogOutput<'a> {
35    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
36        match self {
37            Self::Stdout(lock, _) => lock.write(buf),
38            Self::File(lock) => lock.write(buf),
39        }
40    }
41
42    fn flush(&mut self) -> std::io::Result<()> {
43        match self {
44            Self::Stdout(lock, _) => lock.flush(),
45            Self::File(lock) => lock.flush(),
46        }
47    }
48}
49
50/// Describes the target destination of a [`log`] event.
51///
52/// Locks obtained from these targets are used to create writers to the appropriate [`LogOutput`].
53enum LogDest {
54    /// Log to standard output, with optional color.
55    Stdout(bool),
56    /// Log to a file.
57    File(Mutex<File>),
58}
59
60/// Describes a target destination of a [`log`] event, combined with filters that only permit
61/// specific events to be logged to that target.
62struct LogTarget {
63    /// Target filters. Enables/disables [`Span`](tracing::Span)s based on their target and level.
64    filter: Targets,
65    /// The output destination of the event, if it passes through the filter.
66    dest: LogDest,
67}
68
69/// [`MakeWriter`] implementation for the [`LogLayer`].
70///
71/// Constructs a writer for a specific [`LogTarget`].
72struct LogTargetMakeWriter {
73    stdout: Stdout,
74    target: LogTarget,
75}
76
77impl LogTargetMakeWriter {
78    fn new(target: LogTarget) -> Self {
79        Self {
80            stdout: io::stdout(),
81            target,
82        }
83    }
84
85    fn enabled<S>(&self, meta: &Metadata<'_>, ctx: &Context<'_, S>) -> bool
86    where
87        S: tracing::Subscriber + for<'a> LookupSpan<'a>,
88    {
89        Filter::enabled(&self.target.filter, meta, ctx)
90    }
91}
92
93impl<'a> MakeWriter<'a> for &'a LogTargetMakeWriter {
94    type Writer = LogOutput<'a>;
95
96    fn make_writer(&self) -> Self::Writer {
97        match &self.target.dest {
98            LogDest::Stdout(color) => LogOutput::Stdout(self.stdout.lock(), *color),
99            LogDest::File(file) => LogOutput::File(file.lock()),
100        }
101    }
102}
103
104/// A [`tracing_subscriber::Layer`] for replicating the logging functionality in
105/// [`fern_logger`] without using the [`log`] crate as the global subscriber.
106///
107/// Without this layer, enabling this crate's [`Subscriber`] will disable all logging of any kind, since
108/// it will be used as the global subscriber for the lifetime of the program, and all [`log`] events will
109/// be ignored.
110///
111/// This layer registers an interest in [`Event`](tracing::Event)s that describe [`log`] events,
112/// generated by [`tracing_log`]. These are only created when
113/// [`collect_logs`](crate::subscriber::collect_logs) is called, or a [`LogTracer`](tracing_log::LogTracer)
114/// is initialised.
115pub struct LogLayer {
116    make_writers: Vec<LogTargetMakeWriter>,
117    fmt_events: LogFormatter,
118}
119
120impl<S> Layer<S> for LogLayer
121where
122    S: Subscriber + for<'a> LookupSpan<'a>,
123{
124    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
125        // If the event is originally issued by the `log` crate, generate the appropriate `tracing` metadata.
126        if let Some(metadata) = event.normalized_metadata() {
127            let mut buf = String::new();
128
129            for make_writer in &self.make_writers {
130                // Only write to an output if the event target is enabled by filters.
131                if make_writer.enabled(&metadata, &ctx) {
132                    let mut writer = make_writer.make_writer();
133
134                    if self.fmt_events.format_event(&mut buf, &writer, event).is_ok() {
135                        let _ = io::Write::write(&mut writer, buf.as_bytes());
136                    }
137
138                    buf.clear();
139                }
140            }
141        }
142    }
143}
144
145impl LogLayer {
146    /// The name that specifies the standard output as a log target (instead of a file).
147    const STDOUT_NAME: &'static str = "stdout";
148
149    pub(crate) fn new(config: LoggerConfig) -> Result<Self, Error> {
150        let fmt_events = LogFormatter {
151            target_width: config.target_width(),
152            level_width: config.level_width(),
153        };
154
155        let make_writers = config
156            .outputs()
157            .iter()
158            .map(|output_config: &LoggerOutputConfig| {
159                let level = output_config.level_filter().as_trace();
160
161                let mut targets = if output_config.target_filters().is_empty() {
162                    filter::Targets::default().with_default(level)
163                } else {
164                    let mut targets = filter::Targets::default().with_default(LevelFilter::OFF);
165
166                    for filter in output_config.target_filters() {
167                        targets = targets.with_target(filter.clone().to_lowercase(), level);
168                    }
169
170                    targets
171                };
172
173                for exclusion in output_config.target_exclusions() {
174                    targets = targets.with_target(exclusion.clone().to_lowercase(), LevelFilter::OFF);
175                }
176
177                let dest = match output_config.name() {
178                    Self::STDOUT_NAME => LogDest::Stdout(output_config.color_enabled()),
179                    name => {
180                        let file = OpenOptions::new().write(true).create(true).append(true).open(name)?;
181                        LogDest::File(Mutex::new(file))
182                    }
183                };
184
185                Ok(LogTargetMakeWriter::new(LogTarget { filter: targets, dest }))
186            })
187            .collect::<Result<_, io::Error>>()
188            .map_err(|err| Error::LogLayer(err.into()))?;
189
190        Ok(Self {
191            make_writers,
192            fmt_events,
193        })
194    }
195}
196
197/// Trait that allows a type to be formatted into a [`ColoredString`].
198///
199/// Using a trait here allows this functionality to be implemented for the external [`Level`] type.
200trait ColorFormat {
201    /// Formats `self` into a [`ColoredString`].
202    fn color(self, enabled: bool) -> ColoredString;
203}
204
205impl ColorFormat for Level {
206    fn color(self, enabled: bool) -> ColoredString {
207        let text = self.to_string();
208
209        if !enabled {
210            return text.as_str().into();
211        }
212
213        match self {
214            Level::TRACE => text.bright_magenta(),
215            Level::DEBUG => text.bright_blue(),
216            Level::INFO => text.bright_green(),
217            Level::WARN => text.bright_yellow(),
218            Level::ERROR => text.bright_red(),
219        }
220    }
221}
222
223/// Helper struct for formatting [`log`] records into a [`String`] and writing to a [`Write`](std::fmt::Write)
224/// implementer.
225struct LogFormatter {
226    target_width: usize,
227    level_width: usize,
228}
229
230impl LogFormatter {
231    /// Formats a [`log`] record (converted into a [`tracing::Event`] by [`tracing_log`]) into a [`String`].
232    ///
233    /// This string is then written to a [`Write`](std::fmt::Write) implementer.
234    ///
235    /// Formatting can change depending on the output target of the writer, and so this must also be
236    /// provided. An output that writes to `stdout` can potentially be formatted with text colors.
237    fn format_event<W>(&self, writer: &mut W, output: &LogOutput, event: &Event<'_>) -> std::fmt::Result
238    where
239        W: std::fmt::Write,
240    {
241        if let Some(metadata) = event.normalized_metadata() {
242            let level = *metadata.level();
243            let target = metadata.target();
244
245            let mut visitor = MessageVisitor::default();
246            event.record(&mut visitor);
247
248            let time = time_helper::format(&time_helper::now_utc());
249
250            let level = match *output {
251                LogOutput::File(_) => ColoredString::from(level.to_string().as_str()),
252                LogOutput::Stdout(_, color_enabled) => level.color(color_enabled),
253            };
254
255            write!(
256                writer,
257                "{} {:target_width$} {:level_width$} {}",
258                time,
259                target,
260                level,
261                visitor.0,
262                target_width = self.target_width,
263                level_width = self.level_width,
264            )?;
265
266            writeln!(writer)?;
267        }
268
269        Ok(())
270    }
271}