forked from vercel/turbo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tracing.rs
247 lines (218 loc) · 7.84 KB
/
tracing.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
use std::{marker::PhantomData, sync::Mutex};
use chrono::Local;
use owo_colors::{
colors::{Black, Default, Red, Yellow},
Color, OwoColorize,
};
use tracing::{field::Visit, metadata::LevelFilter, trace, Event, Level, Subscriber};
use tracing_appender::{
non_blocking::{NonBlocking, WorkerGuard},
rolling::RollingFileAppender,
};
use tracing_subscriber::{
filter::Filtered,
fmt::{
self,
format::{DefaultFields, Writer},
FmtContext, FormatEvent, FormatFields,
},
prelude::*,
registry::LookupSpan,
reload::{self, Error, Handle},
EnvFilter, Layer, Registry,
};
use crate::ui::UI;
type StdOutLog = Filtered<
tracing_subscriber::fmt::Layer<Registry, DefaultFields, TurboFormatter>,
EnvFilter,
Registry,
>;
type DaemonLog = tracing_subscriber::fmt::Layer<
Layered,
DefaultFields,
tracing_subscriber::fmt::format::Format,
NonBlocking,
>;
type Layered = tracing_subscriber::layer::Layered<StdOutLog, Registry>;
pub struct TurboSubscriber {
update: Handle<Option<DaemonLog>, Layered>,
/// The non-blocking file logger only continues to log while this guard is
/// held. We keep it here so that it doesn't get dropped.
guard: Mutex<Option<WorkerGuard>>,
#[cfg(feature = "tracing-chrome")]
chrome_guard: tracing_chrome::FlushGuard,
}
impl TurboSubscriber {
/// Sets up the tracing subscriber, with a default stdout layer using the
/// TurboFormatter.
///
/// ## Logging behaviour:
/// - If stdout is a terminal, we use ansi colors. Otherwise, we do not.
/// - If the `TURBO_LOG_VERBOSITY` env var is set, it will be used to set
/// the verbosity level. Otherwise, the default is `WARN`. See the
/// documentation on the RUST_LOG env var for syntax.
/// - If the verbosity argument (usually detemined by a flag) is provided,
/// it overrides the default global log level. This means it overrides the
/// `TURBO_LOG_VERBOSITY` global setting, but not per-module settings.
///
/// Returns a `reload::Handle` that can be used to reload the subscriber.
/// This allows us to register additional layers after setup, for example
/// when configuring logrotation in the daemon.
pub fn new_with_verbosity(verbosity: usize, ui: &UI) -> Self {
let level_override = match verbosity {
0 => None,
1 => Some(LevelFilter::INFO),
2 => Some(LevelFilter::DEBUG),
_ => Some(LevelFilter::TRACE),
};
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.with_env_var("TURBO_LOG_VERBOSITY")
.from_env_lossy();
let filter = if let Some(max_level) = level_override {
filter.add_directive(max_level.into())
} else {
filter
};
let stdout = fmt::layer()
.event_format(TurboFormatter::new_with_ansi(!ui.should_strip_ansi))
.with_filter(filter);
// we set this layer to None to start with, effectively disabling it
let (logrotate, update) = reload::Layer::new(Option::<DaemonLog>::None);
let registry = Registry::default().with(stdout).with(logrotate);
#[cfg(feature = "tracing-chrome")]
let (registry, chrome_guard) = {
let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
.file("./tracing.json")
.build();
(registry.with(chrome_layer), guard)
};
registry.init();
Self {
update,
guard: Mutex::new(None),
#[cfg(feature = "tracing-chrome")]
chrome_guard,
}
}
/// Enables daemon logging with the specified rotation settings.
///
/// Daemon logging uses the standard tracing formatter.
#[tracing::instrument(skip(self))]
pub fn set_daemon_logger(&self, appender: RollingFileAppender) -> Result<(), Error> {
let (file_writer, guard) = tracing_appender::non_blocking(appender);
trace!("created non-blocking file writer");
let layer = tracing_subscriber::fmt::layer().with_writer(file_writer);
self.update.reload(Some(layer))?;
self.guard.lock().expect("not poisoned").replace(guard);
Ok(())
}
}
/// The formatter for TURBOREPO
///
/// This is a port of the go formatter, which follows a few main rules:
/// - Errors are red
/// - Warnings are yellow
/// - Info is default
/// - Debug and trace are default, but with timestamp and level attached
///
/// This formatter does not print any information about spans, and does
/// not print any event metadata other than the message set when you
/// call `debug!(...)` or `info!(...)` etc.
pub struct TurboFormatter {
is_ansi: bool,
}
impl TurboFormatter {
pub fn new_with_ansi(is_ansi: bool) -> Self {
Self { is_ansi }
}
}
impl<S, N> FormatEvent<S, N> for TurboFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let level = event.metadata().level();
let target = event.metadata().target();
match *level {
Level::ERROR => {
write_string::<Red, Black>(writer.by_ref(), self.is_ansi, level.as_str())
.and_then(|_| write_message::<Red, Default>(writer, self.is_ansi, event))
}
Level::WARN => {
write_string::<Yellow, Black>(writer.by_ref(), self.is_ansi, level.as_str())
.and_then(|_| write_message::<Yellow, Default>(writer, self.is_ansi, event))
}
Level::INFO => write_message::<Default, Default>(writer, self.is_ansi, event),
// trace and debug use the same style
_ => {
let now = Local::now();
write!(
writer,
"{} [{}] {}: ",
// build our own timestamp to match the hashicorp/go-hclog format used by the
// go binary
now.format("%Y-%m-%dT%H:%M:%S.%3f%z"),
level,
target,
)
.and_then(|_| write_message::<Default, Default>(writer, self.is_ansi, event))
}
}
}
}
/// A visitor that writes the message field of an event to the given writer.
///
/// The FG and BG type parameters are the foreground and background colors
/// to use when writing the message.
struct MessageVisitor<'a, FG: Color, BG: Color> {
colorize: bool,
writer: Writer<'a>,
_fg: PhantomData<FG>,
_bg: PhantomData<BG>,
}
impl<'a, FG: Color, BG: Color> Visit for MessageVisitor<'a, FG, BG> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
if self.colorize {
let value = value.fg::<FG>().bg::<BG>();
let _ = write!(self.writer, "{:?}", value);
} else {
let _ = write!(self.writer, "{:?}", value);
}
}
}
}
fn write_string<FG: Color, BG: Color>(
mut writer: Writer<'_>,
colorize: bool,
value: &str,
) -> Result<(), std::fmt::Error> {
if colorize {
let value = value.fg::<FG>().bg::<BG>();
write!(writer, "{} ", value)
} else {
write!(writer, "{} ", value)
}
}
/// Writes the message field of an event to the given writer.
fn write_message<FG: Color, BG: Color>(
mut writer: Writer<'_>,
colorize: bool,
event: &Event,
) -> Result<(), std::fmt::Error> {
let mut visitor = MessageVisitor::<FG, BG> {
colorize,
writer: writer.by_ref(),
_fg: PhantomData,
_bg: PhantomData,
};
event.record(&mut visitor);
writeln!(writer)
}