1pub mod format;
5pub mod time;
6pub mod writer;
7
8#[cfg(feature = "schemars")]
9use schemars::JsonSchema;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use std::{fmt, path::PathBuf, str::FromStr};
13use tracing_subscriber::EnvFilter;
14use winnow::{
15 combinator::{alt, preceded, rest},
16 Parser as _,
17};
18
19use writer::Guard;
20
21#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24#[cfg_attr(feature = "schemars", derive(JsonSchema))]
25pub struct Subscriber {
26 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
27 pub format: Option<Format>,
28 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
29 pub writer: Option<Writer>,
30 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
31 pub filter: Option<Filter>,
32}
33
34#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "schemars", derive(JsonSchema))]
37pub struct Filter {
38 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
39 pub regex: Option<bool>,
40 pub directives: Vec<Directive>,
41}
42
43#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
44#[cfg_attr(feature = "schemars", derive(JsonSchema))]
45pub struct Directive(String);
46
47impl Directive {
48 pub const PARSE_HELP: &str = "target[span{field=value}]=level";
49 fn directive(&self) -> tracing_subscriber::filter::Directive {
50 self.0.parse().unwrap()
51 }
52}
53
54impl FromStr for Directive {
55 type Err = tracing_subscriber::filter::ParseError;
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 match s.parse::<tracing_subscriber::filter::Directive>() {
58 Ok(_) => Ok(Self(String::from(s))),
59 Err(e) => Err(e),
60 }
61 }
62}
63
64impl fmt::Display for Directive {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 fmt::Display::fmt(&self.0, f)
67 }
68}
69#[cfg(feature = "serde")]
70impl<'de> Deserialize<'de> for Directive {
71 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
72 stringify::deserialize(d)
73 }
74}
75#[cfg(feature = "serde")]
76impl Serialize for Directive {
77 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
78 stringify::serialize(self, s)
79 }
80}
81
82impl From<Level> for EnvFilter {
83 fn from(value: Level) -> Self {
84 Self::new(value.as_str())
85 }
86}
87
88impl From<Level> for Directive {
89 fn from(value: Level) -> Self {
90 value.as_str().parse().unwrap()
91 }
92}
93
94impl From<Filter> for EnvFilter {
95 fn from(value: Filter) -> Self {
96 let Filter { regex, directives } = value;
97 directives.into_iter().fold(
98 EnvFilter::builder()
99 .with_regex(regex.unwrap_or_default())
100 .parse_lossy(""),
101 |acc, el| acc.add_directive(el.directive()),
102 )
103 }
104}
105
106#[cfg(feature = "serde")]
107mod stringify {
108 use std::{borrow::Cow, fmt, str::FromStr};
109
110 use serde::{Deserialize, Deserializer, Serializer};
111
112 pub fn deserialize<'de, D: Deserializer<'de>, T>(d: D) -> Result<T, D::Error>
113 where
114 T: FromStr,
115 T::Err: fmt::Display,
116 {
117 #[derive(Deserialize)]
118 struct CowStr<'a>(#[serde(borrow)] Cow<'a, str>);
119 let CowStr(s) = Deserialize::deserialize(d)?;
120 s.parse().map_err(serde::de::Error::custom)
121 }
122 pub fn serialize<S: Serializer, T>(t: &T, s: S) -> Result<S::Ok, S::Error>
123 where
124 T: fmt::Display,
125 {
126 s.collect_str(t)
127 }
128}
129
130#[derive(Debug)]
131pub struct ParseError(&'static str);
132
133impl fmt::Display for ParseError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.write_fmt(format_args!("expected {}", self.0))
136 }
137}
138
139impl std::error::Error for ParseError {}
140
141macro_rules! strum {
142 (
143 $(#[$enum_meta:meta])*
144 $vis:vis enum $enum_name:ident $parse_help:literal {
145 $(
146 $(#[$variant_meta:meta])*
147 $variant_name:ident = $string:literal
148 ),* $(,)?
149 }
150 ) => {
151 $(#[$enum_meta])*
152 $vis enum $enum_name {
153 $(
154 $(#[$variant_meta])*
155 $variant_name,
156 )*
157 }
158 impl $enum_name {
159 pub const PARSE_HELP: &str = $parse_help;
160 pub const fn as_str(&self) -> &'static str {
161 match *self {
162 $(
163 Self::$variant_name => $string,
164 )*
165 }
166 }
167 }
168 impl core::fmt::Display for $enum_name {
169 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
170 f.write_str(self.as_str())
171 }
172 }
173 impl core::str::FromStr for $enum_name {
174 type Err = ParseError;
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 match s {
177 $(
178 $string => Ok(Self::$variant_name),
179 )*
180 _ => Err(ParseError(Self::PARSE_HELP))
181 }
182 }
183 }
184 };
185}
186
187strum! {
188#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
189#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
190#[cfg_attr(feature = "schemars", derive(JsonSchema))]
191#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
192pub enum Level "<off|error|warn|info|debug|trace>" {
193 Off = "off",
194 Error = "error",
195 Warn = "warn",
196 #[default]
197 Info = "info",
198 Debug = "debug",
199 Trace = "trace",
200}}
201
202impl From<Level> for tracing_core::LevelFilter {
203 fn from(value: Level) -> Self {
204 match value {
205 Level::Off => Self::OFF,
206 Level::Error => Self::ERROR,
207 Level::Warn => Self::WARN,
208 Level::Info => Self::INFO,
209 Level::Debug => Self::DEBUG,
210 Level::Trace => Self::TRACE,
211 }
212 }
213}
214
215pub type SubscriberBuilder<
217 N = format::FormatFields,
218 E = format::FormatEvent,
219 F = EnvFilter,
220 W = writer::MakeWriter,
221> = tracing_subscriber::fmt::SubscriberBuilder<N, E, F, W>;
222
223pub type Layer<S, N = format::FormatFields, E = format::FormatEvent, W = writer::MakeWriter> =
225 tracing_subscriber::fmt::Layer<S, N, E, W>;
226
227impl Subscriber {
228 fn into_components(
229 self,
230 defer: bool,
231 ) -> Result<
232 (
233 writer::MakeWriter,
234 format::FormatFields,
235 format::FormatEvent,
236 EnvFilter,
237 Guard,
238 ),
239 writer::Error,
240 > {
241 let Self {
242 format,
243 writer,
244 filter,
245 } = self;
246 let format = format.unwrap_or_default();
247 let writer = writer.unwrap_or_default();
248 let (writer, guard) = match defer {
249 true => writer::MakeWriter::try_new(writer)?,
250 false => writer::MakeWriter::new(writer),
251 };
252 let fields = format::FormatFields::from(format.formatter.clone().unwrap_or_default());
253 let event = format::FormatEvent::from(format);
254 let filter = EnvFilter::from(filter.unwrap_or_default());
255 Ok((writer, fields, event, filter, guard))
256 }
257 pub fn layer<S>(self) -> (Layer<S>, Guard)
264 where
265 S: tracing_core::Subscriber + for<'s> tracing_subscriber::registry::LookupSpan<'s>,
266 {
267 let (writer, fields, event, _filter, guard) = self
268 .into_components(true)
269 .expect("errors have been deferred");
270 let layer = tracing_subscriber::fmt::layer()
271 .fmt_fields(fields)
272 .event_format(event)
273 .with_writer(writer);
274 (layer, guard)
275 }
276 pub fn try_layer<S>(self) -> Result<(Layer<S>, Guard), writer::Error>
283 where
284 S: tracing_core::Subscriber + for<'s> tracing_subscriber::registry::LookupSpan<'s>,
285 {
286 let (writer, fields, event, _filter, guard) = self.into_components(false)?;
287 let layer = tracing_subscriber::fmt::layer()
288 .fmt_fields(fields)
289 .event_format(event)
290 .with_writer(writer);
291 Ok((layer, guard))
292 }
293 pub fn builder(self) -> (SubscriberBuilder, Guard) {
298 let (writer, fields, event, filter, guard) = self
299 .into_components(true)
300 .expect("errors have been deferred");
301 let builder = tracing_subscriber::fmt()
302 .fmt_fields(fields)
303 .event_format(event)
304 .with_writer(writer)
305 .with_env_filter(filter);
306 (builder, guard)
307 }
308 pub fn try_builder(self) -> Result<(SubscriberBuilder, Guard), writer::Error> {
313 let (writer, fields, event, filter, guard) = self.into_components(false)?;
314 let builder = tracing_subscriber::fmt()
315 .fmt_fields(fields)
316 .event_format(event)
317 .with_writer(writer)
318 .with_env_filter(filter);
319 Ok((builder, guard))
320 }
321}
322
323#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
326#[cfg_attr(feature = "schemars", derive(JsonSchema))]
327pub struct Format {
328 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
330 pub ansi: Option<bool>,
331 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
333 pub target: Option<bool>,
334 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
336 pub level: Option<bool>,
337 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
339 pub thread_ids: Option<bool>,
340 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
342 pub thread_names: Option<bool>,
343 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
345 pub file: Option<bool>,
346 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
348 pub line_number: Option<bool>,
349 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351 pub formatter: Option<Formatter>,
352 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
354 pub timer: Option<Timer>,
355}
356
357#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
359#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
360#[cfg_attr(feature = "schemars", derive(JsonSchema))]
361#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
362pub enum Formatter {
363 #[default]
365 Full,
366 Compact,
368 Pretty,
370 Json(Option<Json>),
372}
373
374impl Formatter {
375 pub const PARSE_HELP: &str = "<full|compact|pretty|json>";
376}
377
378impl FromStr for Formatter {
379 type Err = ParseError;
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 Ok(match s {
382 "full" => Self::Full,
383 "compact" => Self::Compact,
384 "pretty" => Self::Pretty,
385 "json" => Self::Json(None),
386 _ => return Err(ParseError(Self::PARSE_HELP)),
387 })
388 }
389}
390
391#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
392#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
393#[cfg_attr(feature = "schemars", derive(JsonSchema))]
394pub struct Json {
395 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397 pub flatten_event: Option<bool>,
398 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
400 pub current_span: Option<bool>,
401 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
403 pub span_list: Option<bool>,
404}
405
406#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
408#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
409#[cfg_attr(feature = "schemars", derive(JsonSchema))]
410#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
411pub enum Timer {
412 None,
414 Local(
416 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417 Option<String>,
418 ),
419 Utc(
421 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
422 Option<String>,
423 ),
424 #[default]
426 System,
427 Uptime,
429}
430
431impl Timer {
432 pub const PARSE_HELP: &str = "<none| local[=FORMAT] | utc[=FORMAT] |system|uptime>";
433}
434
435impl FromStr for Timer {
436 type Err = ParseError;
437 fn from_str(s: &str) -> Result<Self, Self::Err> {
438 alt::<_, _, winnow::error::ErrorKind, _>((
439 "none".map(|_| Self::None),
440 preceded("local=", rest).map(|it| Self::Local(Some(String::from(it)))),
441 "local".map(|_| Self::Local(None)),
442 preceded("utc=", rest).map(|it| Self::Utc(Some(String::from(it)))),
443 "utc".map(|_| Self::Utc(None)),
444 "system".map(|_| Self::System),
445 "uptime".map(|_| Self::Uptime),
446 ))
447 .parse(s)
448 .map_err(|_| ParseError(Self::PARSE_HELP))
449 }
450}
451
452#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
454#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
455#[cfg_attr(feature = "schemars", derive(JsonSchema))]
456#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
457pub struct File {
458 pub path: PathBuf,
459 pub behaviour: FileOpenBehaviour,
460 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
462 pub non_blocking: Option<NonBlocking>,
463}
464
465#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
466#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
467#[cfg_attr(feature = "schemars", derive(JsonSchema))]
468#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
469pub struct Rolling {
471 pub directory: PathBuf,
472 pub roll: Option<Roll>,
473 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
475 pub non_blocking: Option<NonBlocking>,
476}
477
478#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
480#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
481#[cfg_attr(feature = "schemars", derive(JsonSchema))]
482#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
483pub enum Writer {
484 Null,
486 #[default]
488 Stdout,
489 Stderr,
491 File(File),
492 Rolling(Rolling),
493}
494
495impl Writer {
496 pub const PARSE_HELP: &str = "<null|stdout|stderr| file=FILE | rolling=DIRECTORY>";
497}
498
499impl FromStr for Writer {
500 type Err = ParseError;
501
502 fn from_str(s: &str) -> Result<Self, Self::Err> {
503 alt::<_, _, winnow::error::ErrorKind, _>((
504 alt(("null", "none")).map(|_| Self::Null),
505 "stdout".map(|_| Self::Stdout),
506 "stderr".map(|_| Self::Stderr),
507 preceded("file=", rest)
508 .verify(|it| !str::is_empty(it))
509 .map(|it| {
510 Self::File(File {
511 path: PathBuf::from(it),
512 ..Default::default()
513 })
514 }),
515 preceded("rolling=", rest).map(|it| {
516 Self::Rolling(Rolling {
517 directory: PathBuf::from(it),
518 ..Default::default()
519 })
520 }),
521 ))
522 .parse(s)
523 .map_err(|_| ParseError(Self::PARSE_HELP))
524 }
525}
526
527strum! {
528#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
532#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
533#[cfg_attr(feature = "schemars", derive(JsonSchema))]
534#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
535pub enum Rotation "<minutely|hourly|daily|never>" {
536 Minutely = "minutely",
537 Hourly = "hourly",
538 Daily = "daily",
539 #[default]
540 Never = "never",
541}}
542
543#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
545#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
546#[cfg_attr(feature = "schemars", derive(JsonSchema))]
547pub struct Roll {
548 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
550 pub limit: Option<usize>,
551 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
553 pub prefix: Option<String>,
554 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
556 pub suffix: Option<String>,
557 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
559 pub rotation: Option<Rotation>,
560}
561
562strum! {
563#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
567#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
568#[cfg_attr(feature = "schemars", derive(JsonSchema))]
569#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
570pub enum BackpressureBehaviour "<drop|block>" {
571 Drop = "drop",
572 Block = "block",
573}}
574
575strum! {
576#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
578#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
579#[cfg_attr(feature = "schemars", derive(JsonSchema))]
580#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
581pub enum FileOpenBehaviour "<truncate|append>" {
582 #[default]
583 Truncate = "truncate",
584 Append = "append",
585}}
586
587#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
589#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
590#[cfg_attr(feature = "schemars", derive(JsonSchema))]
591#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
592pub struct NonBlocking {
593 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
595 pub buffer_length: Option<usize>,
596 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
597 pub behaviour: Option<BackpressureBehaviour>,
598}
599
600#[cfg(all(test, feature = "schemars"))]
601#[test]
602fn schema() {
603 let s = serde_json::to_string_pretty(&schemars::schema_for!(Subscriber)).unwrap();
604 expect_test::expect_file!["../snapshots/schema.json"].assert_eq(&s);
605}