tracing_configuration/
lib.rs

1//! Configuration-as-a-struct for [`tracing_subscriber::fmt::Subscriber`], to allow
2//! for serializable, dynamic configuration, at the cost of compile-time specialization.
3
4pub 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/// Configuration for a totally dynamic subscriber.
22#[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
215/// A totally dynamically configured [`tracing_subscriber::fmt::SubscriberBuilder`].
216pub 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
223/// A totally dynamically configured [`tracing_subscriber::fmt::Layer`].
224pub 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    /// Create a new [`Layer`], and a [`Guard`] that handles e.g flushing [`NonBlocking`] IO.
258    ///
259    /// Errors when opening files or directories are deferred for the subscriber to handle (typically by logging).
260    /// If you wish to handle them yourself, see [`Self::try_layer`].
261    ///
262    /// Note that filtering is ignored for layers.
263    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    /// Create a new [`Layer`], and a [`Guard`] that handles e.g flushing [`NonBlocking`] IO.
277    ///
278    /// Returns [`Err`] if e.g opening a log file fails.
279    /// If you wish the subscriber to handle them (typically by logging), see [`Self::layer`].
280    ///
281    /// Note that filtering is ignored for layers.
282    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    /// Create a new [`SubscriberBuilder`], and a [`Guard`] that handles e.g flushing [`NonBlocking`] IO.
294    ///
295    /// Errors when opening files or directories are deferred for the subscriber to handle (typically by logging).
296    /// If you wish to handle them yourself, see [`Self::try_builder`].
297    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    /// Create a new [`SubscriberBuilder`], and a [`Guard`] that handles e.g flushing [`NonBlocking`] IO.
309    ///
310    /// Returns [`Err`] if e.g opening a log file fails.
311    /// If you wish the subscriber to handle them (typically by logging), see [`Self::builder`].
312    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/// Config for formatters.
324#[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    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_ansi`].
329    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
330    pub ansi: Option<bool>,
331    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_target`].
332    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
333    pub target: Option<bool>,
334    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_level`].
335    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
336    pub level: Option<bool>,
337    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_thread_ids`].
338    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
339    pub thread_ids: Option<bool>,
340    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_thread_names`].
341    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
342    pub thread_names: Option<bool>,
343    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_file`].
344    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
345    pub file: Option<bool>,
346    /// See [`tracing_subscriber::fmt::SubscriberBuilder::with_line_number`].
347    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
348    pub line_number: Option<bool>,
349    /// Specific output formats.
350    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
351    pub formatter: Option<Formatter>,
352    /// What timing information to include.
353    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
354    pub timer: Option<Timer>,
355}
356
357/// The specific output format.
358#[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    /// See [`tracing_subscriber::fmt::format::Full`].
364    #[default]
365    Full,
366    /// See [`tracing_subscriber::fmt::format::Compact`].
367    Compact,
368    /// See [`tracing_subscriber::fmt::format::Pretty`].
369    Pretty,
370    /// See [`tracing_subscriber::fmt::format::Json`].
371    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    /// See [`tracing_subscriber::fmt::format::Json::flatten_event`].
396    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
397    pub flatten_event: Option<bool>,
398    /// See [`tracing_subscriber::fmt::format::Json::with_current_span`].
399    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
400    pub current_span: Option<bool>,
401    /// See [`tracing_subscriber::fmt::format::Json::with_span_list`].
402    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
403    pub span_list: Option<bool>,
404}
405
406/// Which timer implementation to use.
407#[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    /// See [`tracing_subscriber::fmt::SubscriberBuilder::without_time`].
413    None,
414    /// See [`tracing_subscriber::fmt::time::ChronoLocal`].
415    Local(
416        #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
417        Option<String>,
418    ),
419    /// See [`tracing_subscriber::fmt::time::ChronoUtc`].
420    Utc(
421        #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
422        Option<String>,
423    ),
424    /// See [`tracing_subscriber::fmt::time::SystemTime`].
425    #[default]
426    System,
427    /// See [`tracing_subscriber::fmt::time::Uptime`].
428    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/// Write to a [`File`](std::fs::File).
453#[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    /// Wrap the writer in a [`tracing_appender::non_blocking::NonBlocking`].
461    #[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"))]
469/// Use a [`tracing_appender::rolling::RollingFileAppender`].
470pub struct Rolling {
471    pub directory: PathBuf,
472    pub roll: Option<Roll>,
473    /// Wrap the writer in a [`tracing_appender::non_blocking::NonBlocking`].
474    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
475    pub non_blocking: Option<NonBlocking>,
476}
477
478/// Which writer to use.
479#[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    /// No writer.
485    Null,
486    /// Use [`io::stdout`](std::io::stdout).
487    #[default]
488    Stdout,
489    /// Use [`io::stderr`](std::io::stderr).
490    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/// How often to rotate the [`tracing_appender::rolling::RollingFileAppender`].
529///
530/// See [`tracing_appender::rolling::Rotation`].
531#[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/// Config for [`tracing_appender::rolling::RollingFileAppender`].
544#[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    /// See [`tracing_appender::rolling::Builder::max_log_files`].
549    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
550    pub limit: Option<usize>,
551    /// See [`tracing_appender::rolling::Builder::filename_prefix`].
552    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
553    pub prefix: Option<String>,
554    /// See [`tracing_appender::rolling::Builder::filename_suffix`].
555    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
556    pub suffix: Option<String>,
557    /// See [`tracing_appender::rolling::Builder::rotation`].
558    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
559    pub rotation: Option<Rotation>,
560}
561
562strum! {
563/// How the [`tracing_appender::non_blocking::NonBlocking`] should behave on a full queue.
564///
565/// See [`tracing_appender::non_blocking::NonBlockingBuilder::lossy`].
566#[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/// How to treat a newly created log file in [`Writer::File`].
577#[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/// Configuration for [`tracing_appender::non_blocking::NonBlocking`].
588#[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    /// See [`tracing_appender::non_blocking::NonBlockingBuilder::buffered_lines_limit`].
594    #[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}