tracing_forest/
tag.rs

1//! Supplement events with categorical information.
2//!
3//! # Use cases for tags
4//!
5//! Using tags in trace data can improve readability by distinguishing
6//! between different kinds of trace data such as requests, internal state,
7//! or special operations. An error during a network request could mean a
8//! timeout occurred, while an error in the internal state could mean
9//! corruption. Both are errors, but one should be treated more seriously than
10//! the other, and therefore the two should be easily distinguishable.
11//!
12//! # How to use tags
13//!
14//! Every application has its own preferences for how events should be tagged,
15//! and this can be set via a custom [`TagParser`] in the [`ForestLayer`]. This
16//! works by passing a reference to each incoming [`Event`] to the `TagParser`,
17//! which can then be parsed into an `Option<Tag>` for the `ForestLayer` to use
18//! later.
19//!
20//! Since [`TagParser`] is blanket implemented for all `Fn(&Event) -> Option<Tag>`
21//! the easiest way to create one is to define a top-level function with this type
22//! signature.
23//!
24//! Once the function is defined, it can either be passed directly to [`ForestLayer::new`],
25//! or can be passed to [`Builder::set_tag`].
26//!
27//! [`ForestLayer`]: crate::layer::ForestLayer
28//! [`ForestLayer::new`]: crate::layer::ForestLayer::new
29//! [`Builder::set_tag`]: crate::runtime::Builder::set_tag
30//!
31//! ## Examples
32//!
33//! Declaring and using a custom `TagParser`.
34//! ```
35//! use tracing_forest::{util::*, Tag};
36//!
37//! fn simple_tag(event: &Event) -> Option<Tag> {
38//!     let target = event.metadata().target();
39//!     let level = *event.metadata().level();
40//!
41//!     Some(match target {
42//!         "security" if level == Level::ERROR => Tag::builder()
43//!             .prefix(target)
44//!             .suffix("critical")
45//!             .icon('🔐')
46//!             .build(),
47//!         "admin" | "request" => Tag::builder().prefix(target).level(level).build(),
48//!         _ => return None,
49//!     })
50//! }
51//!
52//! #[tokio::main]
53//! async fn main() {
54//!     tracing_forest::worker_task()
55//!         .set_tag(simple_tag)
56//!         .build()
57//!         .on(async {
58//!             // Since `simple_tag` reads from the `target`, we use the target.
59//!             // If it parsed the event differently, we would reflect that here.
60//!             info!(target: "admin", "some info for the admin");
61//!             error!(target: "request", "the request timed out");
62//!             error!(target: "security", "the db has been breached");
63//!             info!("no tags here");
64//!         })
65//!         .await;
66//! }
67//! ```
68//! ```log
69//! INFO     i [admin.info]: some info for the admin
70//! ERROR    🚨 [request.error]: the request timed out
71//! ERROR    🔐 [security.critical]: the db has been breached
72//! INFO     i [info]: no tags here
73//! ```
74use crate::cfg_serde;
75use std::fmt;
76use tracing::{Event, Level};
77
78/// A basic `Copy` type containing information about where an event occurred.
79///
80/// See the [module-level documentation](mod@crate::tag) for more details.
81#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
82pub struct Tag {
83    /// Optional prefix for the tag message
84    prefix: Option<&'static str>,
85
86    /// Level specifying the importance of the log.
87    ///
88    /// This value isn't necessarily "trace", "debug", "info", "warn", or "error",
89    /// and can be customized.
90    suffix: &'static str,
91
92    /// An icon, typically emoji, that represents the tag.
93    icon: char,
94}
95
96impl Tag {
97    /// Build a new [`Tag`].
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use tracing_forest::Tag;
103    ///
104    /// let tag = Tag::builder()
105    ///     .prefix("security")
106    ///     .suffix("critical")
107    ///     .icon('🔐')
108    ///     .build();
109    /// ```
110    pub fn builder() -> Builder<(), ()> {
111        Builder {
112            prefix: None,
113            suffix: (),
114            icon: (),
115        }
116    }
117
118    /// Returns the prefix, if there is one.
119    pub const fn prefix(&self) -> Option<&'static str> {
120        self.prefix
121    }
122
123    /// Returns the suffix.
124    pub const fn suffix(&self) -> &'static str {
125        self.suffix
126    }
127
128    /// Returns the icon.
129    pub const fn icon(&self) -> char {
130        self.icon
131    }
132}
133
134/// Incrementally construct [`Tag`]s.
135///
136/// See [`Tag::builder`] for more details.
137#[derive(Copy, Clone, PartialEq, Eq)]
138pub struct Builder<S, I> {
139    prefix: Option<&'static str>,
140    suffix: S,
141    icon: I,
142}
143
144/// A type used by [`Builder`] to indicate that the suffix has been set.
145#[derive(Copy, Clone, PartialEq, Eq)]
146pub struct Suffix(&'static str);
147
148/// A type used by [`Builder`] to indicate that the icon has been set.
149#[derive(Copy, Clone, PartialEq, Eq)]
150pub struct Icon(char);
151
152impl<S, I> Builder<S, I> {
153    /// Set the prefix.
154    pub fn prefix(self, prefix: &'static str) -> Builder<S, I> {
155        Builder {
156            prefix: Some(prefix),
157            ..self
158        }
159    }
160
161    /// Set the suffix.
162    pub fn suffix(self, suffix: &'static str) -> Builder<Suffix, I> {
163        Builder {
164            prefix: self.prefix,
165            suffix: Suffix(suffix),
166            icon: self.icon,
167        }
168    }
169
170    /// Set the icon.
171    pub fn icon(self, icon: char) -> Builder<S, Icon> {
172        Builder {
173            prefix: self.prefix,
174            suffix: self.suffix,
175            icon: Icon(icon),
176        }
177    }
178
179    /// Set the suffix and icon using defaults for each [`Level`].
180    ///
181    /// If the `Tag` won't have a prefix, then `Tag::from(level)` can be used as
182    /// a shorter alternative.
183    pub fn level(self, level: Level) -> Builder<Suffix, Icon> {
184        let (suffix, icon) = match level {
185            Level::TRACE => ("trace", '📍'),
186            Level::DEBUG => ("debug", '🐛'),
187            Level::INFO => ("info", 'i'),
188            Level::WARN => ("warn", '🚧'),
189            Level::ERROR => ("error", '🚨'),
190        };
191
192        Builder {
193            prefix: self.prefix,
194            suffix: Suffix(suffix),
195            icon: Icon(icon),
196        }
197    }
198}
199
200impl Builder<Suffix, Icon> {
201    /// Complete the [`Tag`].
202    ///
203    /// This can only be called once a suffix and an icon have been provided via
204    /// [`.suffix(...)`](Builder::suffix) and [`.icon(...)`](Builder::icon), or
205    /// alternatively just [`.level(...)`](Builder::level).
206    pub fn build(self) -> Tag {
207        Tag {
208            prefix: self.prefix,
209            suffix: self.suffix.0,
210            icon: self.icon.0,
211        }
212    }
213}
214
215impl fmt::Display for Tag {
216    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217        if let Some(prefix) = self.prefix {
218            write!(f, "{}.{}", prefix, self.suffix)
219        } else {
220            self.suffix.fmt(f)
221        }
222    }
223}
224
225impl From<Level> for Tag {
226    fn from(level: Level) -> Self {
227        Tag::builder().level(level).build()
228    }
229}
230
231cfg_serde! {
232    use serde::{Serialize, Serializer};
233
234    impl Serialize for Tag {
235        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
236            // This could probably go in a smart string
237            serializer.serialize_str(&self.to_string())
238        }
239    }
240}
241
242/// A type that can parse [`Tag`]s from Tracing events.
243///
244/// This trait is blanket-implemented for all `Fn(&tracing::Event) -> Option<Tag>`,
245/// so top-level `fn`s can be used.
246///
247/// See the [module-level documentation](mod@crate::tag) for more details.
248pub trait TagParser: 'static {
249    /// Parse a tag from a [`tracing::Event`]
250    fn parse(&self, event: &Event) -> Option<Tag>;
251}
252
253/// A `TagParser` that always returns `None`.
254#[derive(Clone, Debug)]
255pub struct NoTag;
256
257impl TagParser for NoTag {
258    fn parse(&self, _event: &Event) -> Option<Tag> {
259        None
260    }
261}
262
263impl<F> TagParser for F
264where
265    F: 'static + Fn(&Event) -> Option<Tag>,
266{
267    fn parse(&self, event: &Event) -> Option<Tag> {
268        self(event)
269    }
270}