1pub mod format;
5pub mod time;
6pub mod writer;
7
8#[cfg(feature = "clap4")]
9use clap::{
10 builder::PossibleValue,
11 builder::{TypedValueParser, ValueParser, ValueParserFactory},
12 ValueEnum,
13};
14#[cfg(feature = "schemars1")]
15use schemars::JsonSchema;
16#[cfg(feature = "serde1")]
17use serde::{Deserialize, Serialize};
18#[cfg(feature = "serde1")]
19use serde_with::*;
20use std::{fmt, path::PathBuf, str::FromStr};
21use tracing_subscriber::{filter::Filtered, fmt::format::FmtSpan, EnvFilter, Layer as _};
22use winnow::{
23 combinator::{alt, preceded},
24 token::rest,
25 Parser as _,
26};
27
28use writer::Guard;
29
30#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
32#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
33#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
34pub struct Subscriber {
35 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
36 pub format: Option<Format>,
37 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
38 pub writer: Option<Writer>,
39 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
40 pub filter: Option<Filter>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
44#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
45#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
46pub struct Filter {
47 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
48 pub regex: Option<bool>,
49 #[cfg_attr(
50 feature = "serde1",
51 serde(
52 default,
53 skip_serializing_if = "Vec::is_empty",
54 with = "As::<Vec<DisplayFromStr>>"
55 )
56 )]
57 #[cfg_attr(feature = "schemars1", schemars(with = "Vec<String>"))]
58 pub directives: Vec<tracing_subscriber::filter::Directive>,
59}
60
61impl From<Filter> for EnvFilter {
62 fn from(value: Filter) -> Self {
63 let Filter { regex, directives } = value;
64 let mut builder = EnvFilter::builder();
65 if let Some(regex) = regex {
66 builder = builder.with_regex(regex)
67 }
68 directives
69 .into_iter()
70 .fold(builder.parse_lossy(""), EnvFilter::add_directive)
71 }
72}
73
74#[derive(Debug)]
75pub struct ParseError(&'static str);
76
77impl fmt::Display for ParseError {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(self.0)
80 }
81}
82
83impl std::error::Error for ParseError {}
84
85pub type SubscriberBuilder<
87 N = format::FormatFields,
88 E = format::FormatEvent,
89 F = EnvFilter,
90 W = writer::MakeWriter,
91> = tracing_subscriber::fmt::SubscriberBuilder<N, E, F, W>;
92
93pub type Layer<S, N = format::FormatFields, E = format::FormatEvent, W = writer::MakeWriter> =
95 Filtered<tracing_subscriber::fmt::Layer<S, N, E, W>, EnvFilter, S>;
96
97impl Subscriber {
98 #[expect(clippy::type_complexity)]
99 fn into_components(
100 self,
101 defer: bool,
102 ) -> Result<
103 (
104 writer::MakeWriter,
105 format::FormatFields,
106 format::FormatEvent,
107 EnvFilter,
108 Guard,
109 Option<FmtSpan>,
110 ),
111 writer::Error,
112 > {
113 let Self {
114 format,
115 writer,
116 filter,
117 } = self;
118 let mut format = format.unwrap_or_default();
119 let writer = writer.unwrap_or_default();
120 let (writer, guard) = match defer {
121 true => writer::MakeWriter::try_new(writer)?,
122 false => writer::MakeWriter::new(writer),
123 };
124 let fields = format::FormatFields::from(format.formatter.clone().unwrap_or_default());
125 let span_events = format.span_events.take();
126 let event = format::FormatEvent::from(format);
127 let filter = EnvFilter::from(filter.unwrap_or_default());
128 Ok((writer, fields, event, filter, guard, span_events))
129 }
130 pub fn layer<S>(self) -> (Layer<S>, Guard)
137 where
138 S: tracing_core::Subscriber + for<'s> tracing_subscriber::registry::LookupSpan<'s>,
139 {
140 let (writer, fields, event, filter, guard, span_events) = self
141 .into_components(true)
142 .expect("errors have been deferred");
143 let layer = tracing_subscriber::fmt::layer()
144 .with_span_events(span_events.unwrap_or(FmtSpan::NONE))
145 .fmt_fields(fields)
146 .event_format(event)
147 .with_writer(writer)
148 .with_filter(filter);
149 (layer, guard)
150 }
151 pub fn try_layer<S>(self) -> Result<(Layer<S>, Guard), writer::Error>
158 where
159 S: tracing_core::Subscriber + for<'s> tracing_subscriber::registry::LookupSpan<'s>,
160 {
161 let (writer, fields, event, filter, guard, span_events) = self.into_components(false)?;
162 let layer = tracing_subscriber::fmt::layer()
163 .with_span_events(span_events.unwrap_or(FmtSpan::NONE))
164 .fmt_fields(fields)
165 .event_format(event)
166 .with_writer(writer)
167 .with_filter(filter);
168 Ok((layer, guard))
169 }
170 pub fn builder(self) -> (SubscriberBuilder, Guard) {
175 let (writer, fields, event, filter, guard, span_events) = self
176 .into_components(true)
177 .expect("errors have been deferred");
178 let builder = tracing_subscriber::fmt()
179 .with_span_events(span_events.unwrap_or(FmtSpan::NONE))
180 .fmt_fields(fields)
181 .event_format(event)
182 .with_writer(writer)
183 .with_env_filter(filter);
184 (builder, guard)
185 }
186 pub fn try_builder(self) -> Result<(SubscriberBuilder, Guard), writer::Error> {
191 let (writer, fields, event, filter, guard, span_events) = self.into_components(false)?;
192 let builder = tracing_subscriber::fmt()
193 .with_span_events(span_events.unwrap_or(FmtSpan::NONE))
194 .fmt_fields(fields)
195 .event_format(event)
196 .with_writer(writer)
197 .with_env_filter(filter);
198 Ok((builder, guard))
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
204#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
205#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
206pub struct Format {
207 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
209 pub ansi: Option<bool>,
210 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
212 pub target: Option<bool>,
213 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
215 pub level: Option<bool>,
216 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
218 pub thread_ids: Option<bool>,
219 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
221 pub thread_names: Option<bool>,
222 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
224 pub file: Option<bool>,
225 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
227 pub line_number: Option<bool>,
228 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
230 pub formatter: Option<Formatter>,
231 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
233 pub timer: Option<Timer>,
234 #[cfg_attr(
236 feature = "serde1",
237 serde(
238 default,
239 skip_serializing_if = "Option::is_none",
240 with = "As::<Option<VecFmtSpan>>"
241 )
242 )]
243 #[cfg_attr(feature = "schemars1", schemars(with = "Option<Vec<FmtSpanItem>>"))]
244 pub span_events: Option<FmtSpan>,
245}
246
247#[cfg(feature = "serde1")]
248struct VecFmtSpan;
249#[cfg(feature = "serde1")]
250impl<'de> DeserializeAs<'de, FmtSpan> for VecFmtSpan {
251 fn deserialize_as<D: serde::Deserializer<'de>>(d: D) -> Result<FmtSpan, D::Error> {
252 Ok(Vec::<FmtSpanItem>::deserialize(d)?
253 .into_iter()
254 .fold(FmtSpan::NONE, |acc, el| {
255 acc & match el {
256 FmtSpanItem::New => FmtSpan::NEW,
257 FmtSpanItem::Enter => FmtSpan::ENTER,
258 FmtSpanItem::Exit => FmtSpan::EXIT,
259 FmtSpanItem::Close => FmtSpan::CLOSE,
260 FmtSpanItem::None => FmtSpan::NONE,
261 FmtSpanItem::Active => FmtSpan::ACTIVE,
262 FmtSpanItem::Full => FmtSpan::FULL,
263 }
264 }))
265 }
266}
267#[cfg(feature = "serde1")]
268impl SerializeAs<FmtSpan> for VecFmtSpan {
269 fn serialize_as<S: serde::Serializer>(source: &FmtSpan, s: S) -> Result<S::Ok, S::Error> {
270 match source.clone() {
271 FmtSpan::NONE => [FmtSpanItem::None].serialize(s),
272 FmtSpan::ACTIVE => [FmtSpanItem::Active].serialize(s),
273 FmtSpan::FULL => [FmtSpanItem::Full].serialize(s),
274 _ => {
275 let mut v = vec![];
276 for (theirs, ours) in [
277 (FmtSpan::NEW, FmtSpanItem::New),
278 (FmtSpan::ENTER, FmtSpanItem::Enter),
279 (FmtSpan::EXIT, FmtSpanItem::Exit),
280 (FmtSpan::CLOSE, FmtSpanItem::Close),
281 ] {
282 if source.clone() & theirs.clone() == theirs {
283 v.push(ours)
284 }
285 }
286 v.serialize(s)
287 }
288 }
289 }
290}
291
292#[cfg(feature = "serde1")]
293#[derive(Serialize, Deserialize)]
294#[serde(rename_all = "lowercase")]
295#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
296enum FmtSpanItem {
297 New,
298 Enter,
299 Exit,
300 Close,
301 None,
302 Active,
303 Full,
304}
305
306#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
308#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
309#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
310#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
311pub enum Formatter {
312 #[default]
314 Full,
315 Compact,
317 Pretty,
319 Json(Option<Json>),
321}
322
323impl FromStr for Formatter {
324 type Err = ParseError;
325 fn from_str(s: &str) -> Result<Self, Self::Err> {
326 Ok(match s {
327 "full" => Self::Full,
328 "compact" => Self::Compact,
329 "pretty" => Self::Pretty,
330 "json" => Self::Json(None),
331 _ => {
332 return Err(ParseError(
333 "Expected one of `full`, `compact`, `pretty`, or `json`",
334 ))
335 }
336 })
337 }
338}
339
340#[cfg(feature = "clap4")]
341impl ValueEnum for Formatter {
342 fn value_variants<'a>() -> &'a [Self] {
343 &[Self::Full, Self::Compact, Self::Pretty, Self::Json(None)]
344 }
345
346 fn to_possible_value(&self) -> Option<PossibleValue> {
347 Some(match self {
348 Formatter::Full => PossibleValue::new("full"),
349 Formatter::Compact => PossibleValue::new("compact"),
350 Formatter::Pretty => PossibleValue::new("pretty"),
351 Formatter::Json(_) => PossibleValue::new("json"),
352 })
353 }
354}
355
356#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
357#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
358#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
359pub struct Json {
360 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
362 pub flatten_event: Option<bool>,
363 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
365 pub current_span: Option<bool>,
366 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
368 pub span_list: Option<bool>,
369}
370
371#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
373#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
374#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
375#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
376pub enum Timer {
377 None,
379 Local(
381 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
382 Option<String>,
383 ),
384 Utc(
386 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
387 Option<String>,
388 ),
389 #[default]
391 System,
392 Uptime,
394}
395
396impl Timer {
397 const PARSE_ERROR: &str = "Expected one of `none`, `local`, `local=<format>`, `utc`, `utc=<format>`, `system`, or `uptime`";
398}
399
400impl FromStr for Timer {
401 type Err = ParseError;
402 fn from_str(s: &str) -> Result<Self, Self::Err> {
403 alt::<_, _, winnow::error::EmptyError, _>((
404 "none".map(|_| Self::None),
405 preceded("local=", rest).map(|it| Self::Local(Some(String::from(it)))),
406 "local".map(|_| Self::Local(None)),
407 preceded("utc=", rest).map(|it| Self::Utc(Some(String::from(it)))),
408 "utc".map(|_| Self::Utc(None)),
409 "system".map(|_| Self::System),
410 "uptime".map(|_| Self::Uptime),
411 ))
412 .parse(s)
413 .map_err(|_| ParseError(Self::PARSE_ERROR))
414 }
415}
416
417#[cfg(feature = "clap4")]
418impl ValueEnum for Timer {
419 fn value_variants<'a>() -> &'a [Self] {
420 const {
421 &[
422 Timer::None,
423 Timer::Local(None),
424 Timer::Local(Some(String::new())),
425 Timer::Utc(None),
426 Timer::Utc(Some(String::new())),
427 Timer::System,
428 Timer::Uptime,
429 ]
430 }
431 }
432 fn to_possible_value(&self) -> Option<PossibleValue> {
433 Some(match self {
434 Timer::None => PossibleValue::new("none"),
435 Timer::Local(None) => PossibleValue::new("local"),
436 Timer::Local(Some(_)) => PossibleValue::new("local=<format>"),
437 Timer::Utc(None) => PossibleValue::new("utc"),
438 Timer::Utc(Some(_)) => PossibleValue::new("utc=<format>"),
439 Timer::System => PossibleValue::new("system"),
440 Timer::Uptime => PossibleValue::new("uptime"),
441 })
442 }
443 fn from_str(input: &str, _ignore_case: bool) -> Result<Self, String> {
444 input.parse().map_err(|ParseError(it)| String::from(it))
445 }
446}
447
448#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
450#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
451#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
452#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
453pub struct File {
454 pub path: PathBuf,
455 pub mode: FileOpenMode,
456 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
458 pub non_blocking: Option<NonBlocking>,
459}
460
461#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
462#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
463#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
464#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
465pub struct Rolling {
467 pub directory: PathBuf,
468 pub roll: Option<Roll>,
469 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
471 pub non_blocking: Option<NonBlocking>,
472}
473
474#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
476#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
477#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
478#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
479pub enum Writer {
480 Null,
482 #[default]
484 Stdout,
485 Stderr,
487 File(File),
488 Rolling(Rolling),
489}
490
491impl Writer {
492 const PARSE_ERROR: &str =
493 "Expected one of `null`, `stdout`, `stderr`, `file=<file>`, or `rolling=<directory>`";
494}
495
496impl FromStr for Writer {
497 type Err = ParseError;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 alt::<_, _, winnow::error::EmptyError, _>((
501 alt(("null", "none")).map(|_| Self::Null),
502 "stdout".map(|_| Self::Stdout),
503 "stderr".map(|_| Self::Stderr),
504 preceded("file=", rest)
505 .verify(|it| !str::is_empty(it))
506 .map(|it| {
507 Self::File(File {
508 path: PathBuf::from(it),
509 ..Default::default()
510 })
511 }),
512 preceded("rolling=", rest).map(|it| {
513 Self::Rolling(Rolling {
514 directory: PathBuf::from(it),
515 ..Default::default()
516 })
517 }),
518 ))
519 .parse(s)
520 .map_err(|_| ParseError(Self::PARSE_ERROR))
521 }
522}
523
524#[cfg(feature = "clap4")]
525impl ValueParserFactory for Writer {
527 type Parser = ValueParser;
528 fn value_parser() -> Self::Parser {
529 #[derive(Clone)]
530 struct _TypedValueParser;
531 impl TypedValueParser for _TypedValueParser {
532 type Value = Writer;
533 fn parse_ref(
534 &self,
535 cmd: &clap::Command,
536 _arg: Option<&clap::Arg>,
537 value: &std::ffi::OsStr,
538 ) -> Result<Self::Value, clap::Error> {
539 value
540 .to_str()
541 .ok_or(clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?
542 .parse()
543 .map_err(|_| {
544 clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd)
545 })
546 }
547 fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
548 Some(Box::new(
549 [
550 PossibleValue::new("null"),
551 PossibleValue::new("stdout"),
552 PossibleValue::new("stderr"),
553 PossibleValue::new("file=<file>"),
554 PossibleValue::new("rolling=<directory>"),
555 ]
556 .into_iter(),
557 ))
558 }
559 }
560 ValueParser::new(_TypedValueParser)
561 }
562}
563
564strum_lite::strum! {
565#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
569#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
570#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
571#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
572#[cfg_attr(feature = "clap4", derive(ValueEnum))]
573pub enum Rotation {
574 Minutely = "minutely",
575 Hourly = "hourly",
576 Daily = "daily",
577 #[default]
578 Never = "never",
579}}
580
581#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
583#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
584#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
585pub struct Roll {
586 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
588 pub limit: Option<usize>,
589 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
591 pub prefix: Option<String>,
592 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
594 pub suffix: Option<String>,
595 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
597 pub rotation: Option<Rotation>,
598}
599
600strum_lite::strum! {
601#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
605#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
606#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
607#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
608#[cfg_attr(feature = "clap4", derive(ValueEnum))]
609pub enum BackpressureBehaviour {
610 Drop = "drop",
611 Block = "block",
612}}
613
614strum_lite::strum! {
615#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
617#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
618#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
619#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
620#[cfg_attr(feature = "clap4", derive(ValueEnum))]
621pub enum FileOpenMode {
622 #[default]
623 Truncate = "truncate",
624 Append = "append",
625}}
626
627#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
629#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
630#[cfg_attr(feature = "schemars1", derive(JsonSchema))]
631#[cfg_attr(feature = "serde1", serde(rename_all = "lowercase"))]
632pub struct NonBlocking {
633 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
635 pub buffer_length: Option<usize>,
636 #[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"))]
637 pub behaviour: Option<BackpressureBehaviour>,
638}
639
640#[cfg(all(test, feature = "schemars1"))]
641#[test]
642fn schema() {
643 let s = serde_json::to_string_pretty(&schemars::schema_for!(Subscriber)).unwrap();
644 expect_test::expect_file!["../snapshots/schema.json"].assert_eq(&s);
645}