tracing_human_layer/
style.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3
4use owo_colors::Style as OwoStyle;
5use tracing::Level;
6use tracing::Metadata;
7
8use crate::ShouldColor;
9
10#[cfg(doc)]
11use crate::HumanLayer;
12
13/// A value that can provide a [`Style`] for a given [`tracing`] event.
14pub trait ProvideStyle {
15    /// Provide a [`Style`] for a given set of [`tracing`] metadata.
16    ///
17    /// In the future we may want to give implementers access to more information, but all events
18    /// contain metadata, so we can provide default implementations for future methods.
19    fn for_metadata(&self, metadata: &'static Metadata<'static>) -> Cow<'_, Style>;
20}
21
22/// A simple [`ProvideStyle`] implementation which stores a style for each [`tracing::Level`].
23#[derive(Debug)]
24pub struct LayerStyles {
25    /// Style for the [`Level::TRACE`] log level.
26    pub trace: Style,
27    /// Style for the [`Level::DEBUG`] log level.
28    pub debug: Style,
29    /// Style for the [`Level::INFO`] log level.
30    pub info: Style,
31    /// Style for the [`Level::WARN`] log level.
32    pub warn: Style,
33    /// Style for the [`Level::ERROR`] log level.
34    pub error: Style,
35}
36
37impl LayerStyles {
38    /// Create the default styles.
39    pub fn new() -> Self {
40        let base = Style {
41            initial_indent_text: "".into(),
42            subsequent_indent_text: "  ".into(),
43            initial_indent: OwoStyle::new(),
44            message: OwoStyle::new(),
45            field_name: OwoStyle::new().bold(),
46            field_value: OwoStyle::new(),
47            span_name: OwoStyle::new(),
48            span_in: OwoStyle::new().dimmed(),
49        };
50
51        Self {
52            trace: Style {
53                initial_indent_text: "TRACE ".into(),
54                initial_indent: base.initial_indent.purple(),
55                message: base.message.dimmed(),
56                field_name: base.field_name.dimmed(),
57                field_value: base.field_value.dimmed(),
58                span_name: base.span_name.dimmed(),
59                ..base.clone()
60            },
61
62            debug: Style {
63                initial_indent_text: "DEBUG ".into(),
64                initial_indent: base.initial_indent.blue(),
65                message: base.message.dimmed(),
66                field_name: base.field_name.dimmed(),
67                field_value: base.field_value.dimmed(),
68                span_name: base.span_name.dimmed(),
69                ..base.clone()
70            },
71
72            info: Style {
73                initial_indent_text: "• ".into(),
74                initial_indent: base.initial_indent.green(),
75                ..base.clone()
76            },
77
78            warn: Style {
79                initial_indent_text: "⚠ ".into(),
80                initial_indent: base.initial_indent.yellow(),
81                message: base.message.yellow(),
82                ..base.clone()
83            },
84
85            error: Style {
86                initial_indent_text: "⚠ ".into(),
87                initial_indent: base.initial_indent.red(),
88                message: base.message.red(),
89                ..base
90            },
91        }
92    }
93
94    /// Get the style for a given level.
95    pub(crate) fn for_level(&self, level: Level) -> Cow<'_, Style> {
96        Cow::Borrowed(match level {
97            Level::TRACE => &self.trace,
98            Level::DEBUG => &self.debug,
99            Level::INFO => &self.info,
100            Level::WARN => &self.warn,
101            Level::ERROR => &self.error,
102        })
103    }
104}
105
106impl Default for LayerStyles {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl ProvideStyle for LayerStyles {
113    fn for_metadata(&self, metadata: &'static Metadata<'static>) -> Cow<'_, Style> {
114        self.for_level(*metadata.level())
115    }
116}
117
118/// The style for formatting a [`tracing`] event.
119///
120/// A [`HumanLayer`] retrieves styles through a [`ProvideStyle`] implementation.
121///
122/// TODO: It should be possible to configure which spans and attributes are printed.
123#[derive(Debug, Clone)]
124pub struct Style {
125    pub(crate) initial_indent_text: Cow<'static, str>,
126    pub(crate) subsequent_indent_text: Cow<'static, str>,
127    pub(crate) initial_indent: OwoStyle,
128    pub(crate) message: OwoStyle,
129    pub(crate) field_name: OwoStyle,
130    pub(crate) field_value: OwoStyle,
131    pub(crate) span_name: OwoStyle,
132    pub(crate) span_in: OwoStyle,
133}
134
135impl Style {
136    pub(crate) fn style_field<'a>(
137        &'a self,
138        color: ShouldColor,
139        name: &'a str,
140        value: &'a str,
141    ) -> StyledField<'a> {
142        StyledField {
143            color,
144            name,
145            name_style: self.field_name,
146            value,
147            value_style: self.field_value,
148        }
149    }
150
151    /// First-line indent text.
152    pub fn with_initial_indent_text(mut self, initial_indent_text: Cow<'static, str>) -> Self {
153        self.initial_indent_text = initial_indent_text;
154        self
155    }
156
157    /// Subsequent indent text.
158    pub fn with_subsequent_indent_text(
159        mut self,
160        subsequent_indent_text: Cow<'static, str>,
161    ) -> Self {
162        self.subsequent_indent_text = subsequent_indent_text;
163        self
164    }
165
166    /// Style for first-line indent text.
167    pub fn with_initial_indent(mut self, initial_indent: OwoStyle) -> Self {
168        self.initial_indent = initial_indent;
169        self
170    }
171
172    /// Style for message text.
173    pub fn with_message(mut self, message: OwoStyle) -> Self {
174        self.message = message;
175        self
176    }
177
178    /// Style for field names.
179    pub fn with_field_name(mut self, field_name: OwoStyle) -> Self {
180        self.field_name = field_name;
181        self
182    }
183
184    /// Style for field values.
185    pub fn with_field_value(mut self, field_value: OwoStyle) -> Self {
186        self.field_value = field_value;
187        self
188    }
189
190    /// Style for span names.
191    pub fn with_span_name(mut self, span_name: OwoStyle) -> Self {
192        self.span_name = span_name;
193        self
194    }
195
196    /// Style for the word `in` when writing that an event is `in span such_and_such`.
197    ///
198    /// The name is a bit clumsy, but it matches [`Style::with_span_name`]...
199    pub fn with_span_in(mut self, span_in: OwoStyle) -> Self {
200        self.span_in = span_in;
201        self
202    }
203}
204
205pub(crate) trait IntoConditionalColor: Display {
206    fn colored(&self, color: ShouldColor, style: OwoStyle) -> ConditionalColor<&Self> {
207        ConditionalColor {
208            inner: self,
209            style,
210            color,
211        }
212    }
213}
214
215impl<T> IntoConditionalColor for T where T: Display {}
216
217/// Like `if_supports_color`, but I control it :)
218pub(crate) struct ConditionalColor<T> {
219    color: ShouldColor,
220    style: OwoStyle,
221    inner: T,
222}
223
224impl<T> Display for ConditionalColor<T>
225where
226    T: Display,
227{
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        match self.color {
230            ShouldColor::Always => self.style.style(&self.inner).fmt(f),
231            ShouldColor::Never => self.inner.fmt(f),
232        }
233    }
234}
235
236pub(crate) struct StyledField<'a> {
237    color: ShouldColor,
238    name: &'a str,
239    name_style: OwoStyle,
240    value: &'a str,
241    value_style: OwoStyle,
242}
243
244impl Display for StyledField<'_> {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        write!(f, "{}", self.name.colored(self.color, self.name_style))?;
247        write!(f, "{}", '='.colored(self.color, self.value_style))?;
248        write!(f, "{}", self.value.colored(self.color, self.value_style))?;
249        Ok(())
250    }
251}