trace_tools/subscriber/layer/
log.rs1use 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
24enum LogOutput<'a> {
28 Stdout(StdoutLock<'a>, bool),
30 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
50enum LogDest {
54 Stdout(bool),
56 File(Mutex<File>),
58}
59
60struct LogTarget {
63 filter: Targets,
65 dest: LogDest,
67}
68
69struct 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
104pub 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 let Some(metadata) = event.normalized_metadata() {
127 let mut buf = String::new();
128
129 for make_writer in &self.make_writers {
130 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 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
197trait ColorFormat {
201 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
223struct LogFormatter {
226 target_width: usize,
227 level_width: usize,
228}
229
230impl LogFormatter {
231 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}