tracing_human_layer/
layer.rs

1use std::fmt::Debug;
2use std::io::LineWriter;
3use std::io::Stderr;
4use std::io::Write;
5use std::sync::atomic::AtomicBool;
6use std::sync::atomic::Ordering;
7
8use parking_lot::Mutex;
9use tracing::span;
10use tracing::Event;
11use tracing::Id;
12use tracing::Metadata;
13use tracing::Subscriber;
14use tracing_subscriber::fmt::format::FmtSpan;
15use tracing_subscriber::fmt::FormattedFields;
16use tracing_subscriber::layer::Context;
17use tracing_subscriber::registry::LookupSpan;
18use tracing_subscriber::registry::Scope;
19use tracing_subscriber::Layer;
20
21use crate::HumanEvent;
22use crate::HumanFields;
23use crate::LayerStyles;
24use crate::ProvideStyle;
25use crate::ShouldColor;
26use crate::SpanInfo;
27use crate::StyledSpanFields;
28use crate::TextWrapOptionsOwned;
29
30#[cfg(doc)]
31use crate::Style;
32
33/// A human-friendly [`tracing_subscriber::Layer`].
34pub struct HumanLayer<W = LineWriter<Stderr>, S = LayerStyles> {
35    /// We print blank lines before and after long log messages to help visually separate them.
36    ///
37    /// This becomes an issue if two long log messages are printed one after another.
38    ///
39    /// If this variable is `true`, we skip the blank line before to prevent printing two blank
40    /// lines in a row.
41    ///
42    /// This variable is mutated whenever a [`HumanEvent`] is displayed.
43    last_event_was_long: AtomicBool,
44    /// Which span events to emit.
45    span_events: FmtSpan,
46    /// Whether to color the output.
47    color_output: ShouldColor,
48    /// Options for wrapping text, if any.
49    textwrap_options: Option<TextWrapOptionsOwned>,
50    /// The writer where output is written.
51    output_writer: Mutex<W>,
52    /// Styles for writing events.
53    styles: S,
54}
55
56impl<W, S> Debug for HumanLayer<W, S> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.debug_struct("HumanLayer")
59            .field("span_events", &self.span_events)
60            .field("color_output", &self.color_output)
61            .field("textwrap_options", &self.textwrap_options)
62            // These strings get debug-formatted, which is a bit ugly, but it's fine.
63            // See: https://github.com/rust-lang/rust/issues/117729
64            .field("output_writer", &std::any::type_name::<W>())
65            .field("styles", &std::any::type_name::<S>())
66            .finish_non_exhaustive()
67    }
68}
69
70impl Default for HumanLayer {
71    fn default() -> Self {
72        Self {
73            last_event_was_long: Default::default(),
74            span_events: FmtSpan::NONE,
75            color_output: ShouldColor::Always,
76            output_writer: Mutex::new(LineWriter::new(std::io::stderr())),
77            styles: LayerStyles::new(),
78            textwrap_options: Some(TextWrapOptionsOwned::new()),
79        }
80    }
81}
82
83impl HumanLayer {
84    /// Construct a new [`HumanLayer`] that writes to [`Stderr`].
85    pub fn new() -> Self {
86        Self::default()
87    }
88}
89
90impl<W, S> HumanLayer<W, S> {
91    /// Set the writer that log messages are written to.
92    ///
93    /// This does not change colored output by default.
94    ///
95    /// The `output_writer` should implement [`std::io::Write`] for the [`HumanLayer`] to
96    /// implement [`tracing_subscriber::Layer`].
97    pub fn with_output_writer<W2>(self, output_writer: W2) -> HumanLayer<W2, S> {
98        HumanLayer {
99            last_event_was_long: self.last_event_was_long,
100            span_events: self.span_events,
101            color_output: self.color_output,
102            output_writer: Mutex::new(output_writer),
103            styles: self.styles,
104            textwrap_options: self.textwrap_options,
105        }
106    }
107
108    /// Set the [`textwrap::Options`].
109    ///
110    /// If `None`, no text wrapping is performed.
111    pub fn with_textwrap_options(mut self, textwrap_options: Option<TextWrapOptionsOwned>) -> Self {
112        self.textwrap_options = textwrap_options;
113        self
114    }
115
116    /// Set the output coloring.
117    pub fn with_color_output(mut self, color_output: bool) -> Self {
118        // TODO: Should we expose `ShouldColor` and take it as a parameter here?
119        self.color_output = if color_output {
120            ShouldColor::Always
121        } else {
122            ShouldColor::Never
123        };
124        self
125    }
126
127    /// Set which span events are logged.
128    pub fn with_span_events(mut self, span_events: FmtSpan) -> Self {
129        self.span_events = span_events;
130        self
131    }
132
133    /// Set the output style to the given [`ProvideStyle`] implementation, which supplies
134    /// [`Style`]s.
135    pub fn with_style_provider<S2>(self, styles: S2) -> HumanLayer<W, S2> {
136        HumanLayer {
137            last_event_was_long: self.last_event_was_long,
138            span_events: self.span_events,
139            color_output: self.color_output,
140            output_writer: self.output_writer,
141            styles,
142            textwrap_options: self.textwrap_options,
143        }
144    }
145
146    fn update_long(&self, last_event_was_long: AtomicBool) {
147        self.last_event_was_long
148            .store(last_event_was_long.load(Ordering::SeqCst), Ordering::SeqCst);
149    }
150}
151
152impl<W, S> HumanLayer<W, S>
153where
154    S: ProvideStyle,
155{
156    fn event<R>(
157        &self,
158        metadata: &'static Metadata<'static>,
159        scope: Option<Scope<'_, R>>,
160    ) -> HumanEvent<'_>
161    where
162        R: tracing::Subscriber,
163        R: for<'lookup> LookupSpan<'lookup>,
164    {
165        HumanEvent {
166            // Note: We load the value out of our `AtomicBool` and then clone it to create a _new_
167            // `AtomicBool`. After writing an event, we update _our_ `AtomicBool`.
168            last_event_was_long: self.last_event_was_long.load(Ordering::SeqCst).into(),
169            style: self.styles.for_metadata(metadata),
170            color: self.color_output,
171            spans: scope
172                .map(|scope| SpanInfo::from_scope(scope))
173                .unwrap_or_default(),
174            fields: HumanFields::new_event(),
175            textwrap_options: self.textwrap_options.as_ref().map(|options| options.into()),
176        }
177    }
178
179    fn event_for_id<U>(&self, id: &Id, ctx: Context<'_, U>) -> HumanEvent<'_>
180    where
181        U: tracing::Subscriber,
182        U: for<'lookup> LookupSpan<'lookup>,
183    {
184        self.event(
185            ctx.metadata(id)
186                .expect("Metadata should exist for the span ID"),
187            ctx.span_scope(id),
188        )
189    }
190}
191
192impl<Sub, Wr, Sty> Layer<Sub> for HumanLayer<Wr, Sty>
193where
194    Sub: Subscriber,
195    Sub: for<'lookup> LookupSpan<'lookup>,
196    Self: 'static,
197    Wr: Write,
198    Sty: ProvideStyle,
199{
200    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &Id, ctx: Context<'_, Sub>) {
201        let mut fields = HumanFields::new_span();
202        attrs.record(&mut fields);
203        if let Some(span_ref) = ctx.span(id) {
204            span_ref
205                .extensions_mut()
206                .insert(FormattedFields::<HumanLayer>::new(
207                    StyledSpanFields {
208                        style: self.styles.for_metadata(attrs.metadata()),
209                        color: self.color_output,
210                        fields,
211                    }
212                    .to_string(),
213                ));
214
215            if self.span_events.clone() & FmtSpan::NEW != FmtSpan::NONE {
216                let mut human_event = self.event(span_ref.metadata(), ctx.span_scope(id));
217                human_event.fields.message = Some("new".into());
218                let _ = write!(self.output_writer.lock(), "{human_event}");
219                self.update_long(human_event.last_event_was_long);
220            }
221        }
222    }
223
224    fn on_record(&self, id: &Id, values: &span::Record<'_>, ctx: Context<'_, Sub>) {
225        let mut fields = HumanFields::new_span();
226        values.record(&mut fields);
227        if let Some(span_ref) = ctx.span(id) {
228            span_ref
229                .extensions_mut()
230                .insert(FormattedFields::<HumanLayer>::new(
231                    StyledSpanFields {
232                        style: self.styles.for_metadata(span_ref.metadata()),
233                        color: self.color_output,
234                        fields,
235                    }
236                    .to_string(),
237                ));
238        }
239    }
240
241    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, Sub>) {
242        let mut human_event = self.event(event.metadata(), ctx.event_scope(event));
243        event.record(&mut human_event);
244        let _ = write!(self.output_writer.lock(), "{human_event}");
245        self.update_long(human_event.last_event_was_long);
246    }
247
248    fn on_enter(&self, id: &Id, ctx: Context<'_, Sub>) {
249        if self.span_events.clone() & FmtSpan::ENTER != FmtSpan::NONE {
250            let mut human_event = self.event_for_id(id, ctx);
251            human_event.fields.message = Some("enter".into());
252            let _ = write!(self.output_writer.lock(), "{human_event}");
253            self.update_long(human_event.last_event_was_long);
254        }
255    }
256
257    fn on_exit(&self, id: &Id, ctx: Context<'_, Sub>) {
258        if self.span_events.clone() & FmtSpan::EXIT != FmtSpan::NONE {
259            let mut human_event = self.event_for_id(id, ctx);
260            human_event.fields.message = Some("exit".into());
261            let _ = write!(self.output_writer.lock(), "{human_event}");
262            self.update_long(human_event.last_event_was_long);
263        }
264    }
265
266    fn on_close(&self, id: Id, ctx: Context<'_, Sub>) {
267        if self.span_events.clone() & FmtSpan::CLOSE != FmtSpan::NONE {
268            let mut human_event = self.event_for_id(&id, ctx);
269            human_event.fields.message = Some("close".into());
270            let _ = write!(self.output_writer.lock(), "{human_event}");
271            self.update_long(human_event.last_event_was_long);
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use expect_test::expect;
279
280    use super::*;
281
282    #[test]
283    fn test_debug() {
284        expect![[r#"
285            HumanLayer {
286                span_events: FmtSpan::NONE,
287                color_output: Always,
288                textwrap_options: Some(
289                    TextWrapOptionsOwned {
290                        width: Fixed(
291                            80,
292                        ),
293                        line_ending: LF,
294                        break_words: false,
295                        wrap_algorithm: OptimalFit(
296                            Penalties {
297                                nline_penalty: 1000,
298                                overflow_penalty: 2500,
299                                short_last_line_fraction: 4,
300                                short_last_line_penalty: 25,
301                                hyphen_penalty: 25,
302                            },
303                        ),
304                        word_separator: AsciiSpace,
305                        word_splitter: NoHyphenation,
306                    },
307                ),
308                output_writer: "std::io::buffered::linewriter::LineWriter<std::io::stdio::Stderr>",
309                styles: "tracing_human_layer::style::LayerStyles",
310                ..
311            }"#]]
312        .assert_eq(&format!("{:#?}", HumanLayer::new()));
313    }
314}