rocket_community/trace/subscriber/
pretty.rs

1use 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}