| //! Abstractions for creating [`io::Write`] instances. |
| //! |
| //! [`io::Write`]: std::io::Write |
| use std::{ |
| fmt, |
| io::{self, Write}, |
| sync::{Arc, Mutex, MutexGuard}, |
| }; |
| use tracing_core::Metadata; |
| |
| /// A type that can create [`io::Write`] instances. |
| /// |
| /// `MakeWriter` is used by [`fmt::Layer`] or [`fmt::Subscriber`] to print |
| /// formatted text representations of [`Event`]s. |
| /// |
| /// This trait is already implemented for function pointers and |
| /// immutably-borrowing closures that return an instance of [`io::Write`], such |
| /// as [`io::stdout`] and [`io::stderr`]. Additionally, it is implemented for |
| /// [`std::sync::Mutex`] when the type inside the mutex implements |
| /// [`io::Write`]. |
| /// |
| /// # Examples |
| /// |
| /// The simplest usage is to pass in a named function that returns a writer. For |
| /// example, to log all events to stderr, we could write: |
| /// ``` |
| /// let subscriber = tracing_subscriber::fmt() |
| /// .with_writer(std::io::stderr) |
| /// .finish(); |
| /// # drop(subscriber); |
| /// ``` |
| /// |
| /// Any function that returns a writer can be used: |
| /// |
| /// ``` |
| /// fn make_my_great_writer() -> impl std::io::Write { |
| /// // ... |
| /// # std::io::stdout() |
| /// } |
| /// |
| /// let subscriber = tracing_subscriber::fmt() |
| /// .with_writer(make_my_great_writer) |
| /// .finish(); |
| /// # drop(subscriber); |
| /// ``` |
| /// |
| /// A closure can be used to introduce arbitrary logic into how the writer is |
| /// created. Consider the (admittedly rather silly) example of sending every 5th |
| /// event to stderr, and all other events to stdout: |
| /// |
| /// ``` |
| /// use std::io; |
| /// use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; |
| /// |
| /// let n = AtomicUsize::new(0); |
| /// let subscriber = tracing_subscriber::fmt() |
| /// .with_writer(move || -> Box<dyn io::Write> { |
| /// if n.fetch_add(1, Relaxed) % 5 == 0 { |
| /// Box::new(io::stderr()) |
| /// } else { |
| /// Box::new(io::stdout()) |
| /// } |
| /// }) |
| /// .finish(); |
| /// # drop(subscriber); |
| /// ``` |
| /// |
| /// A single instance of a type implementing [`io::Write`] may be used as a |
| /// `MakeWriter` by wrapping it in a [`Mutex`]. For example, we could |
| /// write to a file like so: |
| /// |
| /// ``` |
| /// use std::{fs::File, sync::Mutex}; |
| /// |
| /// # fn docs() -> Result<(), Box<dyn std::error::Error>> { |
| /// let log_file = File::create("my_cool_trace.log")?; |
| /// let subscriber = tracing_subscriber::fmt() |
| /// .with_writer(Mutex::new(log_file)) |
| /// .finish(); |
| /// # drop(subscriber); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// [`io::Write`]: std::io::Write |
| /// [`fmt::Layer`]: crate::fmt::Layer |
| /// [`fmt::Subscriber`]: crate::fmt::Subscriber |
| /// [`Event`]: tracing_core::event::Event |
| /// [`io::stdout`]: std::io::stdout() |
| /// [`io::stderr`]: std::io::stderr() |
| /// [`MakeWriter::make_writer_for`]: MakeWriter::make_writer_for |
| /// [`Metadata`]: tracing_core::Metadata |
| /// [levels]: tracing_core::Level |
| /// [targets]: tracing_core::Metadata::target |
| pub trait MakeWriter<'a> { |
| /// The concrete [`io::Write`] implementation returned by [`make_writer`]. |
| /// |
| /// [`io::Write`]: std::io::Write |
| /// [`make_writer`]: MakeWriter::make_writer |
| type Writer: io::Write; |
| |
| /// Returns an instance of [`Writer`]. |
| /// |
| /// # Implementer notes |
| /// |
| /// [`fmt::Layer`] or [`fmt::Subscriber`] will call this method each time an event is recorded. Ensure any state |
| /// that must be saved across writes is not lost when the [`Writer`] instance is dropped. If |
| /// creating a [`io::Write`] instance is expensive, be sure to cache it when implementing |
| /// [`MakeWriter`] to improve performance. |
| /// |
| /// [`Writer`]: MakeWriter::Writer |
| /// [`fmt::Layer`]: crate::fmt::Layer |
| /// [`fmt::Subscriber`]: crate::fmt::Subscriber |
| /// [`io::Write`]: std::io::Write |
| fn make_writer(&'a self) -> Self::Writer; |
| |
| /// Returns a [`Writer`] for writing data from the span or event described |
| /// by the provided [`Metadata`]. |
| /// |
| /// By default, this calls [`self.make_writer()`][make_writer], ignoring |
| /// the provided metadata, but implementations can override this to provide |
| /// metadata-specific behaviors. |
| /// |
| /// This method allows `MakeWriter` implementations to implement different |
| /// behaviors based on the span or event being written. The `MakeWriter` |
| /// type might return different writers based on the provided metadata, or |
| /// might write some values to the writer before or after providing it to |
| /// the caller. |
| /// |
| /// For example, we might want to write data from spans and events at the |
| /// [`ERROR`] and [`WARN`] levels to `stderr`, and data from spans or events |
| /// at lower levels to stdout: |
| /// |
| /// ``` |
| /// use std::io::{self, Stdout, Stderr, StdoutLock, StderrLock}; |
| /// use tracing_subscriber::fmt::writer::MakeWriter; |
| /// use tracing_core::{Metadata, Level}; |
| /// |
| /// pub struct MyMakeWriter { |
| /// stdout: Stdout, |
| /// stderr: Stderr, |
| /// } |
| /// |
| /// /// A lock on either stdout or stderr, depending on the verbosity level |
| /// /// of the event being written. |
| /// pub enum StdioLock<'a> { |
| /// Stdout(StdoutLock<'a>), |
| /// Stderr(StderrLock<'a>), |
| /// } |
| /// |
| /// impl<'a> io::Write for StdioLock<'a> { |
| /// fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| /// match self { |
| /// StdioLock::Stdout(lock) => lock.write(buf), |
| /// StdioLock::Stderr(lock) => lock.write(buf), |
| /// } |
| /// } |
| /// |
| /// fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| /// // ... |
| /// # match self { |
| /// # StdioLock::Stdout(lock) => lock.write_all(buf), |
| /// # StdioLock::Stderr(lock) => lock.write_all(buf), |
| /// # } |
| /// } |
| /// |
| /// fn flush(&mut self) -> io::Result<()> { |
| /// // ... |
| /// # match self { |
| /// # StdioLock::Stdout(lock) => lock.flush(), |
| /// # StdioLock::Stderr(lock) => lock.flush(), |
| /// # } |
| /// } |
| /// } |
| /// |
| /// impl<'a> MakeWriter<'a> for MyMakeWriter { |
| /// type Writer = StdioLock<'a>; |
| /// |
| /// fn make_writer(&'a self) -> Self::Writer { |
| /// // We must have an implementation of `make_writer` that makes |
| /// // a "default" writer without any configuring metadata. Let's |
| /// // just return stdout in that case. |
| /// StdioLock::Stdout(self.stdout.lock()) |
| /// } |
| /// |
| /// fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| /// // Here's where we can implement our special behavior. We'll |
| /// // check if the metadata's verbosity level is WARN or ERROR, |
| /// // and return stderr in that case. |
| /// if meta.level() <= &Level::WARN { |
| /// return StdioLock::Stderr(self.stderr.lock()); |
| /// } |
| /// |
| /// // Otherwise, we'll return stdout. |
| /// StdioLock::Stdout(self.stdout.lock()) |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// [`Writer`]: MakeWriter::Writer |
| /// [`Metadata`]: tracing_core::Metadata |
| /// [make_writer]: MakeWriter::make_writer |
| /// [`WARN`]: tracing_core::Level::WARN |
| /// [`ERROR`]: tracing_core::Level::ERROR |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| let _ = meta; |
| self.make_writer() |
| } |
| } |
| |
| /// Extension trait adding combinators for working with types implementing |
| /// [`MakeWriter`]. |
| /// |
| /// This is not intended to be implemented directly for user-defined |
| /// [`MakeWriter`]s; instead, it should be imported when the desired methods are |
| /// used. |
| pub trait MakeWriterExt<'a>: MakeWriter<'a> { |
| /// Wraps `self` and returns a [`MakeWriter`] that will only write output |
| /// for events at or below the provided verbosity [`Level`]. For instance, |
| /// `Level::TRACE` is considered to be _more verbose` than `Level::INFO`. |
| /// |
| /// Events whose level is more verbose than `level` will be ignored, and no |
| /// output will be written. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use tracing::Level; |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// |
| /// // Construct a writer that outputs events to `stderr` only if the span or |
| /// // event's level is >= WARN (WARN and ERROR). |
| /// let mk_writer = std::io::stderr.with_max_level(Level::WARN); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// |
| /// Writing the `ERROR` and `WARN` levels to `stderr`, and everything else |
| /// to `stdout`: |
| /// |
| /// ``` |
| /// # use tracing::Level; |
| /// # use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// |
| /// let mk_writer = std::io::stderr |
| /// .with_max_level(Level::WARN) |
| /// .or_else(std::io::stdout); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// |
| /// Writing the `ERROR` level to `stderr`, the `INFO` and `WARN` levels to |
| /// `stdout`, and the `INFO` and DEBUG` levels to a file: |
| /// |
| /// ``` |
| /// # use tracing::Level; |
| /// # use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// use std::{sync::Arc, fs::File}; |
| /// # // don't actually create the file when running the tests. |
| /// # fn docs() -> std::io::Result<()> { |
| /// let debug_log = Arc::new(File::create("debug.log")?); |
| /// |
| /// let mk_writer = std::io::stderr |
| /// .with_max_level(Level::ERROR) |
| /// .or_else(std::io::stdout |
| /// .with_max_level(Level::INFO) |
| /// .and(debug_log.with_max_level(Level::DEBUG)) |
| /// ); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// # Ok(()) } |
| /// ``` |
| /// |
| /// [`Level`]: tracing_core::Level |
| /// [`io::Write`]: std::io::Write |
| fn with_max_level(self, level: tracing_core::Level) -> WithMaxLevel<Self> |
| where |
| Self: Sized, |
| { |
| WithMaxLevel::new(self, level) |
| } |
| |
| /// Wraps `self` and returns a [`MakeWriter`] that will only write output |
| /// for events at or above the provided verbosity [`Level`]. |
| /// |
| /// Events whose level is less verbose than `level` will be ignored, and no |
| /// output will be written. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use tracing::Level; |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// |
| /// // Construct a writer that outputs events to `stdout` only if the span or |
| /// // event's level is <= DEBUG (DEBUG and TRACE). |
| /// let mk_writer = std::io::stdout.with_min_level(Level::DEBUG); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// This can be combined with [`MakeWriterExt::with_max_level`] to write |
| /// only within a range of levels: |
| /// |
| /// ``` |
| /// # use tracing::Level; |
| /// # use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// // Only write the `DEBUG` and `INFO` levels to stdout. |
| /// let mk_writer = std::io::stdout |
| /// .with_max_level(Level::DEBUG) |
| /// .with_min_level(Level::INFO) |
| /// // Write the `WARN` and `ERROR` levels to stderr. |
| /// .and(std::io::stderr.with_min_level(Level::WARN)); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// [`Level`]: tracing_core::Level |
| /// [`io::Write`]: std::io::Write |
| fn with_min_level(self, level: tracing_core::Level) -> WithMinLevel<Self> |
| where |
| Self: Sized, |
| { |
| WithMinLevel::new(self, level) |
| } |
| |
| /// Wraps `self` with a predicate that takes a span or event's [`Metadata`] |
| /// and returns a `bool`. The returned [`MakeWriter`]'s |
| /// [`MakeWriter::make_writer_for`] method will check the predicate to |
| /// determine if a writer should be produced for a given span or event. |
| /// |
| /// If the predicate returns `false`, the wrapped [`MakeWriter`]'s |
| /// [`make_writer_for`][mwf] will return [`OptionalWriter::none`][own]. |
| /// Otherwise, it calls the wrapped [`MakeWriter`]'s |
| /// [`make_writer_for`][mwf] method, and returns the produced writer. |
| /// |
| /// This can be used to filter an output based on arbitrary [`Metadata`] |
| /// parameters. |
| /// |
| /// # Examples |
| /// |
| /// Writing events with a specific target to an HTTP access log, and other |
| /// events to stdout: |
| /// |
| /// ``` |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// use std::{sync::Arc, fs::File}; |
| /// # // don't actually create the file when running the tests. |
| /// # fn docs() -> std::io::Result<()> { |
| /// let access_log = Arc::new(File::create("access.log")?); |
| /// |
| /// let mk_writer = access_log |
| /// // Only write events with the target "http::access_log" to the |
| /// // access log file. |
| /// .with_filter(|meta| meta.target() == "http::access_log") |
| /// // Write events with all other targets to stdout. |
| /// .or_else(std::io::stdout); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// Conditionally enabling or disabling a log file: |
| /// ``` |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// use std::{ |
| /// sync::{Arc, atomic::{AtomicBool, Ordering}}, |
| /// fs::File, |
| /// }; |
| /// |
| /// static DEBUG_LOG_ENABLED: AtomicBool = AtomicBool::new(false); |
| /// |
| /// # // don't actually create the file when running the tests. |
| /// # fn docs() -> std::io::Result<()> { |
| /// // Create the debug log file |
| /// let debug_file = Arc::new(File::create("debug.log")?) |
| /// // Enable the debug log only if the flag is enabled. |
| /// .with_filter(|_| DEBUG_LOG_ENABLED.load(Ordering::Acquire)); |
| /// |
| /// // Always write to stdout |
| /// let mk_writer = std::io::stdout |
| /// // Write to the debug file if it's enabled |
| /// .and(debug_file); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// |
| /// // ... |
| /// |
| /// // Later, we can toggle on or off the debug log file. |
| /// DEBUG_LOG_ENABLED.store(true, Ordering::Release); |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| /// |
| /// [`Metadata`]: tracing_core::Metadata |
| /// [mwf]: MakeWriter::make_writer_for |
| /// [own]: EitherWriter::none |
| fn with_filter<F>(self, filter: F) -> WithFilter<Self, F> |
| where |
| Self: Sized, |
| F: Fn(&Metadata<'_>) -> bool, |
| { |
| WithFilter::new(self, filter) |
| } |
| |
| /// Combines `self` with another type implementing [`MakeWriter`], returning |
| /// a new [`MakeWriter`] that produces [writers] that write to *both* |
| /// outputs. |
| /// |
| /// If writing to either writer returns an error, the returned writer will |
| /// return that error. However, both writers will still be written to before |
| /// the error is returned, so it is possible for one writer to fail while |
| /// the other is written to successfully. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// |
| /// // Construct a writer that outputs events to `stdout` *and* `stderr`. |
| /// let mk_writer = std::io::stdout.and(std::io::stderr); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// |
| /// `and` can be used in conjunction with filtering combinators. For |
| /// example, if we want to write to a number of outputs depending on the |
| /// level of an event, we could write: |
| /// |
| /// ``` |
| /// use tracing::Level; |
| /// # use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// use std::{sync::Arc, fs::File}; |
| /// # // don't actually create the file when running the tests. |
| /// # fn docs() -> std::io::Result<()> { |
| /// let debug_log = Arc::new(File::create("debug.log")?); |
| /// |
| /// // Write everything to the debug log. |
| /// let mk_writer = debug_log |
| /// // Write the `ERROR` and `WARN` levels to stderr. |
| /// .and(std::io::stderr.with_max_level(Level::WARN)) |
| /// // Write `INFO` to `stdout`. |
| /// .and(std::io::stdout |
| /// .with_max_level(Level::INFO) |
| /// .with_min_level(Level::INFO) |
| /// ); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// # Ok(()) } |
| /// ``` |
| /// |
| /// [writers]: std::io::Write |
| fn and<B>(self, other: B) -> Tee<Self, B> |
| where |
| Self: Sized, |
| B: MakeWriter<'a> + Sized, |
| { |
| Tee::new(self, other) |
| } |
| |
| /// Combines `self` with another type implementing [`MakeWriter`], returning |
| /// a new [`MakeWriter`] that calls `other`'s [`make_writer`] if `self`'s |
| /// `make_writer` returns [`OptionalWriter::none`][own]. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// use tracing::Level; |
| /// use tracing_subscriber::fmt::writer::MakeWriterExt; |
| /// |
| /// // Produces a writer that writes to `stderr` if the level is >= WARN, |
| /// // or returns `OptionalWriter::none()` otherwise. |
| /// let stderr = std::io::stderr.with_max_level(Level::WARN); |
| /// |
| /// // If the `stderr` `MakeWriter` is disabled by the max level filter, |
| /// // write to stdout instead: |
| /// let mk_writer = stderr.or_else(std::io::stdout); |
| /// |
| /// tracing_subscriber::fmt().with_writer(mk_writer).init(); |
| /// ``` |
| /// |
| /// [`make_writer`]: MakeWriter::make_writer |
| /// [own]: EitherWriter::none |
| fn or_else<W, B>(self, other: B) -> OrElse<Self, B> |
| where |
| Self: MakeWriter<'a, Writer = OptionalWriter<W>> + Sized, |
| B: MakeWriter<'a> + Sized, |
| W: Write, |
| { |
| OrElse::new(self, other) |
| } |
| } |
| |
| /// A writer intended to support [`libtest`'s output capturing][capturing] for use in unit tests. |
| /// |
| /// `TestWriter` is used by [`fmt::Subscriber`] or [`fmt::Layer`] to enable capturing support. |
| /// |
| /// `cargo test` can only capture output from the standard library's [`print!`] macro. See |
| /// [`libtest`'s output capturing][capturing] for more details about output capturing. |
| /// |
| /// Writing to [`io::stdout`] and [`io::stderr`] produces the same results as using |
| /// [`libtest`'s `--nocapture` option][nocapture] which may make the results look unreadable. |
| /// |
| /// [`fmt::Subscriber`]: super::Subscriber |
| /// [`fmt::Layer`]: super::Layer |
| /// [capturing]: https://doc.rust-lang.org/book/ch11-02-running-tests.html#showing-function-output |
| /// [nocapture]: https://doc.rust-lang.org/cargo/commands/cargo-test.html |
| /// [`io::stdout`]: std::io::stdout |
| /// [`io::stderr`]: std::io::stderr |
| /// [`print!`]: std::print! |
| #[derive(Default, Debug)] |
| pub struct TestWriter { |
| _p: (), |
| } |
| |
| /// A writer that erases the specific [`io::Write`] and [`MakeWriter`] types being used. |
| /// |
| /// This is useful in cases where the concrete type of the writer cannot be known |
| /// until runtime. |
| /// |
| /// # Examples |
| /// |
| /// A function that returns a [`Subscriber`] that will write to either stdout or stderr: |
| /// |
| /// ```rust |
| /// # use tracing::Subscriber; |
| /// # use tracing_subscriber::fmt::writer::BoxMakeWriter; |
| /// |
| /// fn dynamic_writer(use_stderr: bool) -> impl Subscriber { |
| /// let writer = if use_stderr { |
| /// BoxMakeWriter::new(std::io::stderr) |
| /// } else { |
| /// BoxMakeWriter::new(std::io::stdout) |
| /// }; |
| /// |
| /// tracing_subscriber::fmt().with_writer(writer).finish() |
| /// } |
| /// ``` |
| /// |
| /// [`Subscriber`]: tracing::Subscriber |
| /// [`io::Write`]: std::io::Write |
| pub struct BoxMakeWriter { |
| inner: Box<dyn for<'a> MakeWriter<'a, Writer = Box<dyn Write + 'a>> + Send + Sync>, |
| name: &'static str, |
| } |
| |
| /// A [writer] that is one of two types implementing [`io::Write`]. |
| /// |
| /// This may be used by [`MakeWriter`] implementations that may conditionally |
| /// return one of two writers. |
| /// |
| /// [writer]: std::io::Write |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub enum EitherWriter<A, B> { |
| /// A writer of type `A`. |
| A(A), |
| /// A writer of type `B`. |
| B(B), |
| } |
| |
| /// A [writer] which may or may not be enabled. |
| /// |
| /// This may be used by [`MakeWriter`] implementations that wish to |
| /// conditionally enable or disable the returned writer based on a span or |
| /// event's [`Metadata`]. |
| /// |
| /// [writer]: std::io::Write |
| pub type OptionalWriter<T> = EitherWriter<T, std::io::Sink>; |
| |
| /// A [`MakeWriter`] combinator that only returns an enabled [writer] for spans |
| /// and events with metadata at or below a specified verbosity [`Level`]. |
| /// |
| /// This is returned by the [`MakeWriterExt::with_max_level`] method. See the |
| /// method documentation for details. |
| /// |
| /// [writer]: std::io::Write |
| /// [`Level`]: tracing_core::Level |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct WithMaxLevel<M> { |
| make: M, |
| level: tracing_core::Level, |
| } |
| |
| /// A [`MakeWriter`] combinator that only returns an enabled [writer] for spans |
| /// and events with metadata at or above a specified verbosity [`Level`]. |
| /// |
| /// This is returned by the [`MakeWriterExt::with_min_level`] method. See the |
| /// method documentation for details. |
| /// |
| /// [writer]: std::io::Write |
| /// [`Level`]: tracing_core::Level |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct WithMinLevel<M> { |
| make: M, |
| level: tracing_core::Level, |
| } |
| |
| /// A [`MakeWriter`] combinator that wraps a [`MakeWriter`] with a predicate for |
| /// span and event [`Metadata`], so that the [`MakeWriter::make_writer_for`] |
| /// method returns [`OptionalWriter::some`][ows] when the predicate returns `true`, |
| /// and [`OptionalWriter::none`][own] when the predicate returns `false`. |
| /// |
| /// This is returned by the [`MakeWriterExt::with_filter`] method. See the |
| /// method documentation for details. |
| /// |
| /// [`Metadata`]: tracing_core::Metadata |
| /// [ows]: EitherWriter::some |
| /// [own]: EitherWriter::none |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct WithFilter<M, F> { |
| make: M, |
| filter: F, |
| } |
| |
| /// Combines a [`MakeWriter`] that returns an [`OptionalWriter`] with another |
| /// [`MakeWriter`], so that the second [`MakeWriter`] is used when the first |
| /// [`MakeWriter`] returns [`OptionalWriter::none`][own]. |
| /// |
| /// This is returned by the [`MakeWriterExt::or_else] method. See the |
| /// method documentation for details. |
| /// |
| /// [own]: EitherWriter::none |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct OrElse<A, B> { |
| inner: A, |
| or_else: B, |
| } |
| |
| /// Combines two types implementing [`MakeWriter`] (or [`std::io::Write`]) to |
| /// produce a writer that writes to both [`MakeWriter`]'s returned writers. |
| /// |
| /// This is returned by the [`MakeWriterExt::and`] method. See the method |
| /// documentation for details. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| pub struct Tee<A, B> { |
| a: A, |
| b: B, |
| } |
| |
| /// A type implementing [`io::Write`] for a [`MutexGuard`] where the type |
| /// inside the [`Mutex`] implements [`io::Write`]. |
| /// |
| /// This is used by the [`MakeWriter`] implementation for [`Mutex`], because |
| /// [`MutexGuard`] itself will not implement [`io::Write`] — instead, it |
| /// _dereferences_ to a type implementing [`io::Write`]. Because [`MakeWriter`] |
| /// requires the `Writer` type to implement [`io::Write`], it's necessary to add |
| /// a newtype that forwards the trait implementation. |
| /// |
| /// [`io::Write`]: std::io::Write |
| /// [`MutexGuard`]: std::sync::MutexGuard |
| /// [`Mutex`]: std::sync::Mutex |
| #[derive(Debug)] |
| pub struct MutexGuardWriter<'a, W>(MutexGuard<'a, W>); |
| |
| /// Implements [`std::io::Write`] for an [`Arc`]<W> where `&W: Write`. |
| /// |
| /// This is an implementation detail of the [`MakeWriter`] impl for [`Arc`]. |
| #[derive(Clone, Debug)] |
| pub struct ArcWriter<W>(Arc<W>); |
| |
| /// A bridge between `fmt::Write` and `io::Write`. |
| /// |
| /// This is used by the timestamp formatting implementation for the `time` |
| /// crate and by the JSON formatter. In both cases, this is needed because |
| /// `tracing-subscriber`'s `FormatEvent`/`FormatTime` traits expect a |
| /// `fmt::Write` implementation, while `serde_json::Serializer` and `time`'s |
| /// `format_into` methods expect an `io::Write`. |
| #[cfg(any(feature = "json", feature = "time"))] |
| pub(in crate::fmt) struct WriteAdaptor<'a> { |
| fmt_write: &'a mut dyn fmt::Write, |
| } |
| |
| impl<'a, F, W> MakeWriter<'a> for F |
| where |
| F: Fn() -> W, |
| W: io::Write, |
| { |
| type Writer = W; |
| |
| fn make_writer(&'a self) -> Self::Writer { |
| (self)() |
| } |
| } |
| |
| impl<'a, W> MakeWriter<'a> for Arc<W> |
| where |
| &'a W: io::Write + 'a, |
| { |
| type Writer = &'a W; |
| fn make_writer(&'a self) -> Self::Writer { |
| self |
| } |
| } |
| |
| impl<'a> MakeWriter<'a> for std::fs::File { |
| type Writer = &'a std::fs::File; |
| fn make_writer(&'a self) -> Self::Writer { |
| self |
| } |
| } |
| |
| // === impl TestWriter === |
| |
| impl TestWriter { |
| /// Returns a new `TestWriter` with the default configuration. |
| pub fn new() -> Self { |
| Self::default() |
| } |
| } |
| |
| impl io::Write for TestWriter { |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| let out_str = String::from_utf8_lossy(buf); |
| print!("{}", out_str); |
| Ok(buf.len()) |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| } |
| |
| impl<'a> MakeWriter<'a> for TestWriter { |
| type Writer = Self; |
| |
| fn make_writer(&'a self) -> Self::Writer { |
| Self::default() |
| } |
| } |
| |
| // === impl BoxMakeWriter === |
| |
| impl BoxMakeWriter { |
| /// Constructs a `BoxMakeWriter` wrapping a type implementing [`MakeWriter`]. |
| /// |
| pub fn new<M>(make_writer: M) -> Self |
| where |
| M: for<'a> MakeWriter<'a> + Send + Sync + 'static, |
| { |
| Self { |
| inner: Box::new(Boxed(make_writer)), |
| name: std::any::type_name::<M>(), |
| } |
| } |
| } |
| |
| impl fmt::Debug for BoxMakeWriter { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.debug_tuple("BoxMakeWriter") |
| .field(&format_args!("<{}>", self.name)) |
| .finish() |
| } |
| } |
| |
| impl<'a> MakeWriter<'a> for BoxMakeWriter { |
| type Writer = Box<dyn Write + 'a>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| self.inner.make_writer() |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| self.inner.make_writer_for(meta) |
| } |
| } |
| |
| struct Boxed<M>(M); |
| |
| impl<'a, M> MakeWriter<'a> for Boxed<M> |
| where |
| M: MakeWriter<'a>, |
| { |
| type Writer = Box<dyn Write + 'a>; |
| |
| fn make_writer(&'a self) -> Self::Writer { |
| let w = self.0.make_writer(); |
| Box::new(w) |
| } |
| |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| let w = self.0.make_writer_for(meta); |
| Box::new(w) |
| } |
| } |
| |
| // === impl Mutex/MutexGuardWriter === |
| |
| impl<'a, W> MakeWriter<'a> for Mutex<W> |
| where |
| W: io::Write + 'a, |
| { |
| type Writer = MutexGuardWriter<'a, W>; |
| |
| fn make_writer(&'a self) -> Self::Writer { |
| MutexGuardWriter(self.lock().expect("lock poisoned")) |
| } |
| } |
| |
| impl<'a, W> io::Write for MutexGuardWriter<'a, W> |
| where |
| W: io::Write, |
| { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.0.write(buf) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.0.flush() |
| } |
| |
| #[inline] |
| fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { |
| self.0.write_vectored(bufs) |
| } |
| |
| #[inline] |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| self.0.write_all(buf) |
| } |
| |
| #[inline] |
| fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { |
| self.0.write_fmt(fmt) |
| } |
| } |
| |
| // === impl EitherWriter === |
| |
| impl<A, B> io::Write for EitherWriter<A, B> |
| where |
| A: io::Write, |
| B: io::Write, |
| { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| match self { |
| EitherWriter::A(a) => a.write(buf), |
| EitherWriter::B(b) => b.write(buf), |
| } |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| match self { |
| EitherWriter::A(a) => a.flush(), |
| EitherWriter::B(b) => b.flush(), |
| } |
| } |
| |
| #[inline] |
| fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { |
| match self { |
| EitherWriter::A(a) => a.write_vectored(bufs), |
| EitherWriter::B(b) => b.write_vectored(bufs), |
| } |
| } |
| |
| #[inline] |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| match self { |
| EitherWriter::A(a) => a.write_all(buf), |
| EitherWriter::B(b) => b.write_all(buf), |
| } |
| } |
| |
| #[inline] |
| fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { |
| match self { |
| EitherWriter::A(a) => a.write_fmt(fmt), |
| EitherWriter::B(b) => b.write_fmt(fmt), |
| } |
| } |
| } |
| |
| impl<T> OptionalWriter<T> { |
| /// Returns a [disabled writer]. |
| /// |
| /// Any bytes written to the returned writer are discarded. |
| /// |
| /// This is equivalent to returning [`Option::None`]. |
| /// |
| /// [disabled writer]: std::io::sink |
| #[inline] |
| pub fn none() -> Self { |
| EitherWriter::B(std::io::sink()) |
| } |
| |
| /// Returns an enabled writer of type `T`. |
| /// |
| /// This is equivalent to returning [`Option::Some`]. |
| #[inline] |
| pub fn some(t: T) -> Self { |
| EitherWriter::A(t) |
| } |
| } |
| |
| impl<T> From<Option<T>> for OptionalWriter<T> { |
| #[inline] |
| fn from(opt: Option<T>) -> Self { |
| match opt { |
| Some(writer) => Self::some(writer), |
| None => Self::none(), |
| } |
| } |
| } |
| |
| // === impl WithMaxLevel === |
| |
| impl<M> WithMaxLevel<M> { |
| /// Wraps the provided [`MakeWriter`] with a maximum [`Level`], so that it |
| /// returns [`OptionalWriter::none`] for spans and events whose level is |
| /// more verbose than the maximum level. |
| /// |
| /// See [`MakeWriterExt::with_max_level`] for details. |
| /// |
| /// [`Level`]: tracing_core::Level |
| pub fn new(make: M, level: tracing_core::Level) -> Self { |
| Self { make, level } |
| } |
| } |
| |
| impl<'a, M: MakeWriter<'a>> MakeWriter<'a> for WithMaxLevel<M> { |
| type Writer = OptionalWriter<M::Writer>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| // If we don't know the level, assume it's disabled. |
| OptionalWriter::none() |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| if meta.level() <= &self.level { |
| return OptionalWriter::some(self.make.make_writer_for(meta)); |
| } |
| OptionalWriter::none() |
| } |
| } |
| |
| // === impl WithMinLevel === |
| |
| impl<M> WithMinLevel<M> { |
| /// Wraps the provided [`MakeWriter`] with a minimum [`Level`], so that it |
| /// returns [`OptionalWriter::none`] for spans and events whose level is |
| /// less verbose than the maximum level. |
| /// |
| /// See [`MakeWriterExt::with_min_level`] for details. |
| /// |
| /// [`Level`]: tracing_core::Level |
| pub fn new(make: M, level: tracing_core::Level) -> Self { |
| Self { make, level } |
| } |
| } |
| |
| impl<'a, M: MakeWriter<'a>> MakeWriter<'a> for WithMinLevel<M> { |
| type Writer = OptionalWriter<M::Writer>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| // If we don't know the level, assume it's disabled. |
| OptionalWriter::none() |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| if meta.level() >= &self.level { |
| return OptionalWriter::some(self.make.make_writer_for(meta)); |
| } |
| OptionalWriter::none() |
| } |
| } |
| |
| // ==== impl WithFilter === |
| |
| impl<M, F> WithFilter<M, F> { |
| /// Wraps `make` with the provided `filter`, returning a [`MakeWriter`] that |
| /// will call `make.make_writer_for()` when `filter` returns `true` for a |
| /// span or event's [`Metadata`], and returns a [`sink`] otherwise. |
| /// |
| /// See [`MakeWriterExt::with_filter`] for details. |
| /// |
| /// [`Metadata`]: tracing_core::Metadata |
| /// [`sink`]: std::io::sink |
| pub fn new(make: M, filter: F) -> Self |
| where |
| F: Fn(&Metadata<'_>) -> bool, |
| { |
| Self { make, filter } |
| } |
| } |
| |
| impl<'a, M, F> MakeWriter<'a> for WithFilter<M, F> |
| where |
| M: MakeWriter<'a>, |
| F: Fn(&Metadata<'_>) -> bool, |
| { |
| type Writer = OptionalWriter<M::Writer>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| OptionalWriter::some(self.make.make_writer()) |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| if (self.filter)(meta) { |
| OptionalWriter::some(self.make.make_writer_for(meta)) |
| } else { |
| OptionalWriter::none() |
| } |
| } |
| } |
| |
| // === impl Tee === |
| |
| impl<A, B> Tee<A, B> { |
| /// Combines two types implementing [`MakeWriter`], returning |
| /// a new [`MakeWriter`] that produces [writers] that write to *both* |
| /// outputs. |
| /// |
| /// See the documentation for [`MakeWriterExt::and`] for details. |
| /// |
| /// [writers]: std::io::Write |
| pub fn new(a: A, b: B) -> Self { |
| Self { a, b } |
| } |
| } |
| |
| impl<'a, A, B> MakeWriter<'a> for Tee<A, B> |
| where |
| A: MakeWriter<'a>, |
| B: MakeWriter<'a>, |
| { |
| type Writer = Tee<A::Writer, B::Writer>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| Tee::new(self.a.make_writer(), self.b.make_writer()) |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| Tee::new(self.a.make_writer_for(meta), self.b.make_writer_for(meta)) |
| } |
| } |
| |
| macro_rules! impl_tee { |
| ($self_:ident.$f:ident($($arg:ident),*)) => { |
| { |
| let res_a = $self_.a.$f($($arg),*); |
| let res_b = $self_.b.$f($($arg),*); |
| (res_a?, res_b?) |
| } |
| } |
| } |
| |
| impl<A, B> io::Write for Tee<A, B> |
| where |
| A: io::Write, |
| B: io::Write, |
| { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| let (a, b) = impl_tee!(self.write(buf)); |
| Ok(std::cmp::max(a, b)) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| impl_tee!(self.flush()); |
| Ok(()) |
| } |
| |
| #[inline] |
| fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { |
| let (a, b) = impl_tee!(self.write_vectored(bufs)); |
| Ok(std::cmp::max(a, b)) |
| } |
| |
| #[inline] |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| impl_tee!(self.write_all(buf)); |
| Ok(()) |
| } |
| |
| #[inline] |
| fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { |
| impl_tee!(self.write_fmt(fmt)); |
| Ok(()) |
| } |
| } |
| |
| // === impl OrElse === |
| |
| impl<A, B> OrElse<A, B> { |
| /// Combines |
| pub fn new<'a, W>(inner: A, or_else: B) -> Self |
| where |
| A: MakeWriter<'a, Writer = OptionalWriter<W>>, |
| B: MakeWriter<'a>, |
| W: Write, |
| { |
| Self { inner, or_else } |
| } |
| } |
| |
| impl<'a, A, B, W> MakeWriter<'a> for OrElse<A, B> |
| where |
| A: MakeWriter<'a, Writer = OptionalWriter<W>>, |
| B: MakeWriter<'a>, |
| W: io::Write, |
| { |
| type Writer = EitherWriter<W, B::Writer>; |
| |
| #[inline] |
| fn make_writer(&'a self) -> Self::Writer { |
| match self.inner.make_writer() { |
| EitherWriter::A(writer) => EitherWriter::A(writer), |
| EitherWriter::B(_) => EitherWriter::B(self.or_else.make_writer()), |
| } |
| } |
| |
| #[inline] |
| fn make_writer_for(&'a self, meta: &Metadata<'_>) -> Self::Writer { |
| match self.inner.make_writer_for(meta) { |
| EitherWriter::A(writer) => EitherWriter::A(writer), |
| EitherWriter::B(_) => EitherWriter::B(self.or_else.make_writer_for(meta)), |
| } |
| } |
| } |
| |
| // === impl ArcWriter === |
| |
| impl<W> io::Write for ArcWriter<W> |
| where |
| for<'a> &'a W: io::Write, |
| { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| (&*self.0).write(buf) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| (&*self.0).flush() |
| } |
| |
| #[inline] |
| fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> { |
| (&*self.0).write_vectored(bufs) |
| } |
| |
| #[inline] |
| fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { |
| (&*self.0).write_all(buf) |
| } |
| |
| #[inline] |
| fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> { |
| (&*self.0).write_fmt(fmt) |
| } |
| } |
| |
| // === impl WriteAdaptor === |
| |
| #[cfg(any(feature = "json", feature = "time"))] |
| impl<'a> WriteAdaptor<'a> { |
| pub(in crate::fmt) fn new(fmt_write: &'a mut dyn fmt::Write) -> Self { |
| Self { fmt_write } |
| } |
| } |
| #[cfg(any(feature = "json", feature = "time"))] |
| impl<'a> io::Write for WriteAdaptor<'a> { |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| let s = |
| std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; |
| |
| self.fmt_write |
| .write_str(s) |
| .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; |
| |
| Ok(s.as_bytes().len()) |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| } |
| |
| #[cfg(any(feature = "json", feature = "time"))] |
| impl<'a> fmt::Debug for WriteAdaptor<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.pad("WriteAdaptor { .. }") |
| } |
| } |
| // === blanket impls === |
| |
| impl<'a, M> MakeWriterExt<'a> for M where M: MakeWriter<'a> {} |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::fmt::format::Format; |
| use crate::fmt::test::{MockMakeWriter, MockWriter}; |
| use crate::fmt::Subscriber; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::{Arc, Mutex}; |
| use tracing::{debug, error, info, trace, warn, Level}; |
| use tracing_core::dispatcher::{self, Dispatch}; |
| |
| fn test_writer<T>(make_writer: T, msg: &str, buf: &Mutex<Vec<u8>>) |
| where |
| T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, |
| { |
| let subscriber = { |
| #[cfg(feature = "ansi")] |
| let f = Format::default().without_time().with_ansi(false); |
| #[cfg(not(feature = "ansi"))] |
| let f = Format::default().without_time(); |
| Subscriber::builder() |
| .event_format(f) |
| .with_writer(make_writer) |
| .finish() |
| }; |
| let dispatch = Dispatch::from(subscriber); |
| |
| dispatcher::with_default(&dispatch, || { |
| error!("{}", msg); |
| }); |
| |
| let expected = format!("ERROR {}: {}\n", module_path!(), msg); |
| let actual = String::from_utf8(buf.try_lock().unwrap().to_vec()).unwrap(); |
| assert!(actual.contains(expected.as_str())); |
| } |
| |
| fn has_lines(buf: &Mutex<Vec<u8>>, msgs: &[(tracing::Level, &str)]) { |
| let actual = String::from_utf8(buf.try_lock().unwrap().to_vec()).unwrap(); |
| let mut expected_lines = msgs.iter(); |
| for line in actual.lines() { |
| let line = dbg!(line).trim(); |
| let (level, msg) = expected_lines |
| .next() |
| .unwrap_or_else(|| panic!("expected no more lines, but got: {:?}", line)); |
| let expected = format!("{} {}: {}", level, module_path!(), msg); |
| assert_eq!(line, expected.as_str()); |
| } |
| } |
| |
| #[test] |
| fn custom_writer_closure() { |
| let buf = Arc::new(Mutex::new(Vec::new())); |
| let buf2 = buf.clone(); |
| let make_writer = move || MockWriter::new(buf2.clone()); |
| let msg = "my custom writer closure error"; |
| test_writer(make_writer, msg, &buf); |
| } |
| |
| #[test] |
| fn custom_writer_struct() { |
| let buf = Arc::new(Mutex::new(Vec::new())); |
| let make_writer = MockMakeWriter::new(buf.clone()); |
| let msg = "my custom writer struct error"; |
| test_writer(make_writer, msg, &buf); |
| } |
| |
| #[test] |
| fn custom_writer_mutex() { |
| let buf = Arc::new(Mutex::new(Vec::new())); |
| let writer = MockWriter::new(buf.clone()); |
| let make_writer = Mutex::new(writer); |
| let msg = "my mutex writer error"; |
| test_writer(make_writer, msg, &buf); |
| } |
| |
| #[test] |
| fn combinators_level_filters() { |
| let info_buf = Arc::new(Mutex::new(Vec::new())); |
| let info = MockMakeWriter::new(info_buf.clone()); |
| |
| let debug_buf = Arc::new(Mutex::new(Vec::new())); |
| let debug = MockMakeWriter::new(debug_buf.clone()); |
| |
| let warn_buf = Arc::new(Mutex::new(Vec::new())); |
| let warn = MockMakeWriter::new(warn_buf.clone()); |
| |
| let err_buf = Arc::new(Mutex::new(Vec::new())); |
| let err = MockMakeWriter::new(err_buf.clone()); |
| |
| let make_writer = info |
| .with_max_level(Level::INFO) |
| .and(debug.with_max_level(Level::DEBUG)) |
| .and(warn.with_max_level(Level::WARN)) |
| .and(err.with_max_level(Level::ERROR)); |
| |
| let c = { |
| #[cfg(feature = "ansi")] |
| let f = Format::default().without_time().with_ansi(false); |
| #[cfg(not(feature = "ansi"))] |
| let f = Format::default().without_time(); |
| Subscriber::builder() |
| .event_format(f) |
| .with_writer(make_writer) |
| .with_max_level(Level::TRACE) |
| .finish() |
| }; |
| |
| let _s = tracing::subscriber::set_default(c); |
| |
| trace!("trace"); |
| debug!("debug"); |
| info!("info"); |
| warn!("warn"); |
| error!("error"); |
| |
| let all_lines = [ |
| (Level::TRACE, "trace"), |
| (Level::DEBUG, "debug"), |
| (Level::INFO, "info"), |
| (Level::WARN, "warn"), |
| (Level::ERROR, "error"), |
| ]; |
| |
| println!("max level debug"); |
| has_lines(&debug_buf, &all_lines[1..]); |
| |
| println!("max level info"); |
| has_lines(&info_buf, &all_lines[2..]); |
| |
| println!("max level warn"); |
| has_lines(&warn_buf, &all_lines[3..]); |
| |
| println!("max level error"); |
| has_lines(&err_buf, &all_lines[4..]); |
| } |
| |
| #[test] |
| fn combinators_or_else() { |
| let some_buf = Arc::new(Mutex::new(Vec::new())); |
| let some = MockMakeWriter::new(some_buf.clone()); |
| |
| let or_else_buf = Arc::new(Mutex::new(Vec::new())); |
| let or_else = MockMakeWriter::new(or_else_buf.clone()); |
| |
| let return_some = AtomicBool::new(true); |
| let make_writer = move || { |
| if return_some.swap(false, Ordering::Relaxed) { |
| OptionalWriter::some(some.make_writer()) |
| } else { |
| OptionalWriter::none() |
| } |
| }; |
| let make_writer = make_writer.or_else(or_else); |
| let c = { |
| #[cfg(feature = "ansi")] |
| let f = Format::default().without_time().with_ansi(false); |
| #[cfg(not(feature = "ansi"))] |
| let f = Format::default().without_time(); |
| Subscriber::builder() |
| .event_format(f) |
| .with_writer(make_writer) |
| .with_max_level(Level::TRACE) |
| .finish() |
| }; |
| |
| let _s = tracing::subscriber::set_default(c); |
| info!("hello"); |
| info!("world"); |
| info!("goodbye"); |
| |
| has_lines(&some_buf, &[(Level::INFO, "hello")]); |
| has_lines( |
| &or_else_buf, |
| &[(Level::INFO, "world"), (Level::INFO, "goodbye")], |
| ); |
| } |
| |
| #[test] |
| fn combinators_or_else_chain() { |
| let info_buf = Arc::new(Mutex::new(Vec::new())); |
| let info = MockMakeWriter::new(info_buf.clone()); |
| |
| let debug_buf = Arc::new(Mutex::new(Vec::new())); |
| let debug = MockMakeWriter::new(debug_buf.clone()); |
| |
| let warn_buf = Arc::new(Mutex::new(Vec::new())); |
| let warn = MockMakeWriter::new(warn_buf.clone()); |
| |
| let err_buf = Arc::new(Mutex::new(Vec::new())); |
| let err = MockMakeWriter::new(err_buf.clone()); |
| |
| let make_writer = err.with_max_level(Level::ERROR).or_else( |
| warn.with_max_level(Level::WARN).or_else( |
| info.with_max_level(Level::INFO) |
| .or_else(debug.with_max_level(Level::DEBUG)), |
| ), |
| ); |
| |
| let c = { |
| #[cfg(feature = "ansi")] |
| let f = Format::default().without_time().with_ansi(false); |
| #[cfg(not(feature = "ansi"))] |
| let f = Format::default().without_time(); |
| Subscriber::builder() |
| .event_format(f) |
| .with_writer(make_writer) |
| .with_max_level(Level::TRACE) |
| .finish() |
| }; |
| |
| let _s = tracing::subscriber::set_default(c); |
| |
| trace!("trace"); |
| debug!("debug"); |
| info!("info"); |
| warn!("warn"); |
| error!("error"); |
| |
| println!("max level debug"); |
| has_lines(&debug_buf, &[(Level::DEBUG, "debug")]); |
| |
| println!("max level info"); |
| has_lines(&info_buf, &[(Level::INFO, "info")]); |
| |
| println!("max level warn"); |
| has_lines(&warn_buf, &[(Level::WARN, "warn")]); |
| |
| println!("max level error"); |
| has_lines(&err_buf, &[(Level::ERROR, "error")]); |
| } |
| |
| #[test] |
| fn combinators_and() { |
| let a_buf = Arc::new(Mutex::new(Vec::new())); |
| let a = MockMakeWriter::new(a_buf.clone()); |
| |
| let b_buf = Arc::new(Mutex::new(Vec::new())); |
| let b = MockMakeWriter::new(b_buf.clone()); |
| |
| let lines = &[(Level::INFO, "hello"), (Level::INFO, "world")]; |
| |
| let make_writer = a.and(b); |
| let c = { |
| #[cfg(feature = "ansi")] |
| let f = Format::default().without_time().with_ansi(false); |
| #[cfg(not(feature = "ansi"))] |
| let f = Format::default().without_time(); |
| Subscriber::builder() |
| .event_format(f) |
| .with_writer(make_writer) |
| .with_max_level(Level::TRACE) |
| .finish() |
| }; |
| |
| let _s = tracing::subscriber::set_default(c); |
| info!("hello"); |
| info!("world"); |
| |
| has_lines(&a_buf, &lines[..]); |
| has_lines(&b_buf, &lines[..]); |
| } |
| } |