rocket_community/trace/subscriber/
pretty.rs1use std::fmt;
2
3use tracing::field::Field;
4use tracing::span::{Attributes, Id, Record};
5use tracing::{Event, Level, Metadata, Subscriber};
6use tracing_subscriber::field::RecordFields;
7use tracing_subscriber::layer::{Context, Layer};
8use tracing_subscriber::registry::LookupSpan;
9
10use yansi::{Paint, Painted};
11
12use crate::trace::subscriber::{Data, RecordDisplay, RocketFmt};
13use crate::util::Formatter;
14
15#[derive(Debug, Default, Copy, Clone)]
16pub struct Pretty {
17 depth: u32,
18}
19
20impl RocketFmt<Pretty> {
21 fn indent(&self) -> &'static str {
22 static INDENT: &[&str] = &["", " ", " "];
23 INDENT
24 .get(self.state().depth as usize)
25 .copied()
26 .unwrap_or(" ")
27 }
28
29 fn marker(&self) -> &'static str {
30 static MARKER: &[&str] = &["", ">> ", ":: "];
31 MARKER
32 .get(self.state().depth as usize)
33 .copied()
34 .unwrap_or("-- ")
35 }
36
37 fn emoji(&self, _emoji: &'static str) -> Painted<&'static str> {
38 #[cfg(windows)]
39 {
40 "".paint(self.style).mask()
41 }
42 #[cfg(not(windows))]
43 {
44 _emoji.paint(self.style).mask()
45 }
46 }
47
48 fn prefix<'a>(&self, meta: &'a Metadata<'_>) -> impl fmt::Display + 'a {
49 let (i, m, s) = (self.indent(), self.marker(), self.style(meta));
50 Formatter(move |f| match *meta.level() {
51 Level::WARN => write!(f, "{i}{m}{} ", "warning:".paint(s).bold()),
52 Level::ERROR => write!(f, "{i}{m}{} ", "error:".paint(s).bold()),
53 Level::INFO => write!(f, "{i}{m}"),
54 level => write!(f, "{i}{m}[{} {}] ", level.paint(s).bold(), meta.target()),
55 })
56 }
57
58 fn print_pretty<F: RecordFields>(&self, m: &Metadata<'_>, data: F) {
59 let prefix = self.prefix(m);
60 let cont_prefix = Formatter(|f| {
61 let style = self.style(m);
62 write!(f, "{}{} ", self.indent(), "++".paint(style).dim())
63 });
64
65 self.print(&prefix, &cont_prefix, m, data);
66 }
67
68 fn print_fields<F>(&self, metadata: &Metadata<'_>, fields: F)
69 where
70 F: RecordFields,
71 {
72 let style = self.style(metadata);
73 let prefix = self.prefix(metadata);
74 fields.record_display(|key: &Field, value: &dyn fmt::Display| {
75 if key.name() != "message" {
76 println!(
77 "{prefix}{}: {}",
78 key.paint(style),
79 value.paint(style).primary()
80 );
81 }
82 })
83 }
84}
85
86impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
87 fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
88 self.filter
89 .would_enable(metadata.target(), metadata.level())
90 }
91
92 fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
93 let (meta, data) = (event.metadata(), Data::new(event));
94 let style = self.style(meta);
95 match meta.name() {
96 "config" => self.print_fields(meta, event),
97 "liftoff" => {
98 let prefix = self.prefix(meta);
99 println!(
100 "{prefix}{}{} {}",
101 self.emoji("🚀 "),
102 "Rocket has launched on".paint(style).primary().bold(),
103 &data["endpoint"].paint(style).primary().bold().underline()
104 );
105 }
106 "route" => println!(
107 "{}",
108 Formatter(|f| {
109 write!(
110 f,
111 "{}{}{}: ",
112 self.indent(),
113 self.marker(),
114 "route".paint(style)
115 )?;
116
117 let (base, mut relative) = (&data["uri.base"], &data["uri.unmounted"]);
118 if base.ends_with('/') && relative.starts_with('/') {
119 relative = &relative[1..];
120 }
121
122 write!(
123 f,
124 "{:>3} {} {}{}",
125 &data["rank"].paint(style.bright().dim()),
126 &data["method"].paint(style.bold()),
127 base.paint(style.primary().underline()),
128 relative.paint(style.primary()),
129 )?;
130
131 if let Some(name) = data.get("name") {
132 write!(f, " ({}", name.paint(style.bold().bright()))?;
133
134 if let Some(location) = data.get("location") {
135 write!(f, " {}", location.paint(style.dim()))?;
136 }
137
138 write!(f, ")")?;
139 }
140
141 Ok(())
142 })
143 ),
144 "catcher" => println!(
145 "{}",
146 Formatter(|f| {
147 write!(
148 f,
149 "{}{}{}: ",
150 self.indent(),
151 self.marker(),
152 "catcher".paint(style)
153 )?;
154
155 match data.get("code") {
156 Some(code) => write!(f, "{} ", code.paint(style.bold()))?,
157 None => write!(f, "{} ", "default".paint(style.bold()))?,
158 }
159
160 write!(f, "{}", &data["uri.base"].paint(style.primary()))?;
161 if let Some(name) = data.get("name") {
162 write!(f, " ({}", name.paint(style.bold().bright()))?;
163
164 if let Some(location) = data.get("location") {
165 write!(f, " {}", location.paint(style.dim()))?;
166 }
167
168 write!(f, ")")?;
169 }
170
171 Ok(())
172 })
173 ),
174 "header" => println!(
175 "{}{}{}: {}: {}",
176 self.indent(),
177 self.marker(),
178 "header".paint(style),
179 &data["name"].paint(style.bold()),
180 &data["value"].paint(style.primary()),
181 ),
182 "fairing" => println!(
183 "{}{}{}: {} {}",
184 self.indent(),
185 self.marker(),
186 "fairing".paint(style),
187 &data["name"].paint(style.bold()),
188 &data["kind"].paint(style.primary().dim()),
189 ),
190 _ => self.print_pretty(meta, event),
191 }
192 }
193
194 fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctxt: Context<'_, S>) {
195 let data = Data::new(attrs);
196 let span = ctxt.span(id).expect("new_span: span does not exist");
197 if &data["count"] != "0" {
198 let name = span.name();
199 let icon = match name {
200 "config" => "🔧 ",
201 "routes" => "📬 ",
202 "catchers" => "🚧 ",
203 "fairings" => "📦 ",
204 "shield" => "🛡️ ",
205 "templating" => "📐 ",
206 "request" => "● ",
207 _ => "",
208 };
209
210 let meta = span.metadata();
211 let style = self.style(meta);
212 let emoji = self.emoji(icon);
213 let name = name.paint(style).bold();
214
215 let fields = self.compact_fields(meta, attrs);
216 let prefix = self.prefix(meta);
217 let fieldless_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} "));
218 let field_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} ({fields}) "));
219
220 if self.has_message(meta) && self.has_data_fields(meta) {
221 print!(
222 "{}",
223 self.message(&field_prefix, &fieldless_prefix, meta, attrs)
224 );
225 } else if self.has_message(meta) {
226 print!(
227 "{}",
228 self.message(&fieldless_prefix, &fieldless_prefix, meta, attrs)
229 );
230 } else if self.has_data_fields(meta) {
231 println!("{field_prefix}");
232 } else {
233 println!("{fieldless_prefix}");
234 }
235 }
236
237 span.extensions_mut().replace(data);
238 }
239
240 fn on_record(&self, id: &Id, values: &Record<'_>, ctxt: Context<'_, S>) {
241 let span = ctxt.span(id).expect("new_span: span does not exist");
242 match span.extensions_mut().get_mut::<Data>() {
243 Some(data) => values.record(data),
244 None => span.extensions_mut().insert(Data::new(values)),
245 }
246
247 let meta = span.metadata();
248 println!("{}{}", self.prefix(meta), self.compact_fields(meta, values));
249 }
250
251 fn on_enter(&self, _: &Id, _: Context<'_, S>) {
252 self.update_state(|state| state.depth = state.depth.saturating_add(1));
253 }
254
255 fn on_exit(&self, _: &Id, _: Context<'_, S>) {
256 self.update_state(|state| state.depth = state.depth.saturating_sub(1));
257 }
258}