rocket/trace/subscriber/
pretty.rs

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