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}