diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8cabb51299..e963c3a550 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -15,7 +15,7 @@ tracing-core = { path = "../tracing-core", version = "0.1"} tracing-error = { path = "../tracing-error" } tracing-flame = { path = "../tracing-flame" } tracing-tower = { version = "0.1.0", path = "../tracing-tower" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.2.4", features = ["json", "chrono"] } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", features = ["json", "chrono"] } tracing-futures = { version = "0.2.1", path = "../tracing-futures", features = ["futures-01"] } tracing-attributes = { path = "../tracing-attributes", version = "0.1.2"} tracing-log = { path = "../tracing-log", version = "0.1.1", features = ["env_logger"] } diff --git a/tracing-appender/Cargo.toml b/tracing-appender/Cargo.toml index e8434e2630..8309497491 100644 --- a/tracing-appender/Cargo.toml +++ b/tracing-appender/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tracing-appender" -version = "0.1.0" +version = "0.2.0" authors = [ "Zeki Sherif ", "Tokio Contributors " @@ -20,7 +20,7 @@ keywords = ["logging", "tracing", "file-appender", "non-blocking-writer"] edition = "2018" [dependencies] -tracing-subscriber = {path = "../tracing-subscriber", version = "0.2.4"} +tracing-subscriber = {path = "../tracing-subscriber", version = "0.3"} crossbeam-channel = "0.4.2" chrono = "0.4.11" diff --git a/tracing-appender/src/non_blocking.rs b/tracing-appender/src/non_blocking.rs index 5cabc0db4b..f7cc324c69 100644 --- a/tracing-appender/src/non_blocking.rs +++ b/tracing-appender/src/non_blocking.rs @@ -236,10 +236,10 @@ impl std::io::Write for NonBlocking { } } -impl MakeWriter for NonBlocking { +impl<'a> MakeWriter<'a> for NonBlocking { type Writer = NonBlocking; - fn make_writer(&self) -> Self::Writer { + fn make_writer(&'a self) -> Self::Writer { self.clone() } } diff --git a/tracing-error/Cargo.toml b/tracing-error/Cargo.toml index 26f5cd4e15..5c85056a22 100644 --- a/tracing-error/Cargo.toml +++ b/tracing-error/Cargo.toml @@ -38,7 +38,7 @@ default = ["traced-error"] traced-error = [] [dependencies] -tracing-subscriber = { path = "../tracing-subscriber", version = "0.2.4", default-features = false, features = ["registry", "fmt"] } +tracing-subscriber = { path = "../tracing-subscriber", version = ">= 0.2.4, < 0.4", default-features = false, features = ["registry", "fmt"] } tracing = { path = "../tracing", version = "0.1.12"} [badges] diff --git a/tracing-flame/Cargo.toml b/tracing-flame/Cargo.toml index c071b08505..1ed78fcce1 100644 --- a/tracing-flame/Cargo.toml +++ b/tracing-flame/Cargo.toml @@ -25,7 +25,7 @@ default = ["smallvec"] smallvec = ["tracing-subscriber/smallvec"] [dependencies] -tracing-subscriber = { path = "../tracing-subscriber", version = "0.2.3", default-features = false, features = ["registry", "fmt"] } +tracing-subscriber = { path = "../tracing-subscriber", version = "0.3", default-features = false, features = ["registry", "fmt"] } tracing = { path = "../tracing", version = "0.1.12"} lazy_static = "1.3.0" diff --git a/tracing-opentelemetry/Cargo.toml b/tracing-opentelemetry/Cargo.toml index 83138a8a1f..5b4de0e996 100644 --- a/tracing-opentelemetry/Cargo.toml +++ b/tracing-opentelemetry/Cargo.toml @@ -26,7 +26,7 @@ opentelemetry = { version = "0.6", default-features = false, features = ["trace" rand = "0.7" tracing = { path = "../tracing", version = "0.1" } tracing-core = { path = "../tracing-core", version = "0.1" } -tracing-subscriber = { path = "../tracing-subscriber", version = "0.2", default-features = false, features = ["registry"] } +tracing-subscriber = { path = "../tracing-subscriber", version = ">= 0.2, < 0.4", default-features = false, features = ["registry"] } tracing-log = { path = "../tracing-log", version = "0.1", default-features = false, optional = true } [dev-dependencies] diff --git a/tracing-subscriber/Cargo.toml b/tracing-subscriber/Cargo.toml index de0af28714..d6810b70fb 100644 --- a/tracing-subscriber/Cargo.toml +++ b/tracing-subscriber/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tracing-subscriber" -version = "0.2.5" +version = "0.3.0" authors = [ "Eliza Weisman ", "David Barsky ", diff --git a/tracing-subscriber/src/fmt/fmt_layer.rs b/tracing-subscriber/src/fmt/fmt_layer.rs index 30660a447f..8bd65ad58a 100644 --- a/tracing-subscriber/src/fmt/fmt_layer.rs +++ b/tracing-subscriber/src/fmt/fmt_layer.rs @@ -107,7 +107,7 @@ impl Layer where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'writer> FormatFields<'writer> + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { /// Sets the [event formatter][`FormatEvent`] that the layer will use to /// format events. @@ -146,7 +146,7 @@ where // This needs to be a seperate impl block because they place different bounds on the type paramaters. impl Layer { - /// Sets the [`MakeWriter`] that the [`Layer`] being built will use to write events. + /// Sets the [`for<'writer> MakeWriter<'writer>`] that the [`Layer`] being built will use to write events. /// /// # Examples /// @@ -163,11 +163,11 @@ impl Layer { /// # let _ = layer.with_subscriber(tracing_subscriber::registry::Registry::default()); /// ``` /// - /// [`MakeWriter`]: ../fmt/trait.MakeWriter.html + /// [`for<'writer> MakeWriter<'writer>`]: ../fmt/trait.for<'writer> MakeWriter<'writer>.html /// [`Layer`]: ../layer/trait.Layer.html pub fn with_writer(self, make_writer: W2) -> Layer where - W2: MakeWriter + 'static, + W2: for<'writer> MakeWriter<'writer> + 'static, { Layer { fmt_fields: self.fmt_fields, @@ -326,7 +326,7 @@ where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { /// Builds a [`Layer`] with the provided configuration. /// @@ -356,7 +356,7 @@ where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { #[inline] fn make_ctx<'a>(&'a self, ctx: Context<'a, S>) -> FmtContext<'a, S, N> { @@ -421,7 +421,7 @@ where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { fn new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { let span = ctx.span(id).expect("Span not found, this is a bug"); @@ -490,7 +490,7 @@ where unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { // This `downcast_raw` impl allows downcasting a `fmt` layer to any of - // its components (event formatter, field formatter, and `MakeWriter`) + // its components (event formatter, field formatter, and `for<'writer> MakeWriter<'writer>`) // as well as to the layer's type itself. The potential use-cases for // this *may* be somewhat niche, though... match () { diff --git a/tracing-subscriber/src/fmt/format/json.rs b/tracing-subscriber/src/fmt/format/json.rs index 8ff2ce0baa..184469ad62 100644 --- a/tracing-subscriber/src/fmt/format/json.rs +++ b/tracing-subscriber/src/fmt/format/json.rs @@ -366,11 +366,10 @@ impl<'a> fmt::Debug for WriteAdaptor<'a> { #[cfg(test)] mod test { - use crate::fmt::{test::MockWriter, time::FormatTime}; - use lazy_static::lazy_static; + use crate::fmt::{test::MockMakeWriter, time::FormatTime}; use tracing::{self, subscriber::with_default}; - use std::{fmt, sync::Mutex}; + use std::fmt; struct MockTime; impl FormatTime for MockTime { @@ -381,45 +380,33 @@ mod test { #[test] fn json() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let expected = "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"target\":\"tracing_subscriber::fmt::format::json::test\",\"fields\":{\"message\":\"some json test\"}}\n"; - test_json(make_writer, expected, &BUF, false); + test_json(expected, false); } #[test] fn json_flattened_event() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let expected = "{\"timestamp\":\"fake time\",\"level\":\"INFO\",\"span\":{\"answer\":42,\"name\":\"json_span\",\"number\":3},\"target\":\"tracing_subscriber::fmt::format::json::test\",\"message\":\"some json test\"}\n"; - test_json(make_writer, expected, &BUF, true); + test_json(expected, true); } #[test] fn record_works() { // This test reproduces issue #707, where using `Span::record` causes // any events inside the span to be ignored. - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - let make_writer = || MockWriter::new(&BUF); - let subscriber = crate::fmt().json().with_writer(make_writer).finish(); + let make_writer = MockMakeWriter::default(); + let subscriber = crate::fmt() + .json() + .with_writer(make_writer.clone()) + .finish(); let parse_buf = || -> serde_json::Value { - let buf = String::from_utf8(BUF.try_lock().unwrap().to_vec()).unwrap(); + let buf = String::from_utf8(make_writer.buf().to_vec()).unwrap(); let json = buf .lines() .last() @@ -453,14 +440,12 @@ mod test { } #[cfg(feature = "json")] - fn test_json(make_writer: T, expected: &str, buf: &Mutex>, flatten_event: bool) - where - T: crate::fmt::MakeWriter + Send + Sync + 'static, - { + fn test_json(expected: &str, flatten_event: bool) { + let make_writer = MockMakeWriter::default(); let subscriber = crate::fmt::Subscriber::builder() .json() .flatten_event(flatten_event) - .with_writer(make_writer) + .with_writer(make_writer.clone()) .with_timer(MockTime) .finish(); @@ -470,7 +455,7 @@ mod test { tracing::info!("some json test"); }); - let actual = String::from_utf8(buf.try_lock().unwrap().to_vec()).unwrap(); + let actual = String::from_utf8(make_writer.buf().to_vec()).unwrap(); assert_eq!(expected, actual.as_str()); } } diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 14f1f5dbb8..dde16b620a 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -825,12 +825,10 @@ impl<'a, F> fmt::Debug for FieldFnVisitor<'a, F> { #[cfg(test)] mod test { + use crate::fmt::{test::MockMakeWriter, time::FormatTime}; + use tracing::dispatcher::{set_default, Dispatch}; - use crate::fmt::{test::MockWriter, time::FormatTime}; - use lazy_static::lazy_static; - use tracing::{self, subscriber::with_default}; - - use std::{fmt, sync::Mutex}; + use std::fmt; struct MockTime; impl FormatTime for MockTime { @@ -842,90 +840,55 @@ mod test { #[cfg(feature = "ansi")] #[test] fn with_ansi_true() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m tracing_subscriber::fmt::format::test: some ansi test\n"; - test_ansi(make_writer, expected, true, &BUF); + let expected = "\u{1b}[2mfake time\u{1b}[0m \u{1b}[32m INFO\u{1b}[0m tracing_subscriber::fmt::format::test: hello\n"; + test_ansi(true, expected); } #[cfg(feature = "ansi")] #[test] fn with_ansi_false() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let expected = "fake time INFO tracing_subscriber::fmt::format::test: some ansi test\n"; - - test_ansi(make_writer, expected, false, &BUF); + let expected = "fake time INFO tracing_subscriber::fmt::format::test: hello\n"; + test_ansi(false, expected); } #[cfg(not(feature = "ansi"))] #[test] fn without_ansi() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let expected = "fake time INFO tracing_subscriber::fmt::format::test: some ansi test\n"; + let make_writer = MockMakeWriter::default(); + let expected = "fake time INFO tracing_subscriber::fmt::format::test: hello\n"; let subscriber = crate::fmt::Subscriber::builder() .with_writer(make_writer) - .with_timer(MockTime) - .finish(); + .with_timer(MockTime); + run_test(subscriber, make_writer, expected); + } - with_default(subscriber, || { - tracing::info!("some ansi test"); - }); + #[test] + fn without_level() { + let make_writer = MockMakeWriter::default(); + let subscriber = crate::fmt::Subscriber::builder() + .with_writer(make_writer.clone()) + .with_level(false) + .with_ansi(false) + .with_timer(MockTime); + let expected = "fake time tracing_subscriber::fmt::format::test: hello\n"; - let actual = String::from_utf8(BUF.try_lock().unwrap().to_vec()).unwrap(); - assert_eq!(expected, actual.as_str()); + run_test(subscriber, make_writer, expected); } #[cfg(feature = "ansi")] - fn test_ansi(make_writer: T, expected: &str, is_ansi: bool, buf: &Mutex>) - where - T: crate::fmt::MakeWriter + Send + Sync + 'static, - { + fn test_ansi(is_ansi: bool, expected: &str) { + let make_writer = MockMakeWriter::default(); let subscriber = crate::fmt::Subscriber::builder() - .with_writer(make_writer) + .with_writer(make_writer.clone()) .with_ansi(is_ansi) - .with_timer(MockTime) - .finish(); - - with_default(subscriber, || { - tracing::info!("some ansi test"); - }); - - let actual = String::from_utf8(buf.try_lock().unwrap().to_vec()).unwrap(); - assert_eq!(expected, actual.as_str()); + .with_timer(MockTime); + run_test(subscriber, make_writer, expected) } - #[test] - fn without_level() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); - let subscriber = crate::fmt::Subscriber::builder() - .with_writer(make_writer) - .with_level(false) - .with_ansi(false) - .with_timer(MockTime) - .finish(); - - with_default(subscriber, || { - tracing::info!("hello"); - }); - let actual = String::from_utf8(BUF.try_lock().unwrap().to_vec()).unwrap(); - assert_eq!( - "fake time tracing_subscriber::fmt::format::test: hello\n", - actual.as_str() - ); + fn run_test(subscriber: impl Into, buf: MockMakeWriter, expected: &str) { + let _default = set_default(&subscriber.into()); + tracing::info!("hello"); + let actual = String::from_utf8(buf.buf().to_vec()).unwrap(); + assert_eq!(expected, actual.as_str()) } } diff --git a/tracing-subscriber/src/fmt/mod.rs b/tracing-subscriber/src/fmt/mod.rs index f8be96c88a..a7b2c29342 100644 --- a/tracing-subscriber/src/fmt/mod.rs +++ b/tracing-subscriber/src/fmt/mod.rs @@ -293,7 +293,7 @@ where N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, F: layer::Layer> + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, layer::Layered>: tracing_core::Subscriber, fmt_layer::Layer: layer::Layer, { @@ -388,7 +388,7 @@ impl SubscriberBuilder where N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, F: layer::Layer> + Send + Sync + 'static, fmt_layer::Layer: layer::Layer + Send + Sync + 'static, { @@ -438,7 +438,7 @@ impl Into for SubscriberBuilder where N: for<'writer> FormatFields<'writer> + 'static, E: FormatEvent + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, F: layer::Layer> + Send + Sync + 'static, fmt_layer::Layer: layer::Layer + Send + Sync + 'static, { @@ -731,7 +731,7 @@ impl SubscriberBuilder { where E2: FormatEvent + 'static, N: for<'writer> FormatFields<'writer> + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { SubscriberBuilder { filter: self.filter, @@ -754,7 +754,7 @@ impl SubscriberBuilder { where E2: FormatEvent + 'static, N: for<'writer> FormatFields<'writer> + 'static, - W: MakeWriter + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, { self.event_format(fmt_event) } @@ -777,7 +777,7 @@ impl SubscriberBuilder { /// [`MakeWriter`]: trait.MakeWriter.html pub fn with_writer(self, make_writer: W2) -> SubscriberBuilder where - W2: MakeWriter + 'static, + W2: for<'writer> MakeWriter<'writer> + 'static, { SubscriberBuilder { filter: self.filter, @@ -856,16 +856,16 @@ mod test { }; use std::{ io, - sync::{Mutex, MutexGuard, TryLockError}, + sync::{Arc, Mutex, MutexGuard, TryLockError}, }; use tracing_core::dispatcher::Dispatch; - pub(crate) struct MockWriter<'a> { - buf: &'a Mutex>, + pub(crate) struct MockWriter { + buf: Arc>>, } - impl<'a> MockWriter<'a> { - pub(crate) fn new(buf: &'a Mutex>) -> Self { + impl MockWriter { + pub(crate) fn new(buf: Arc>>) -> Self { Self { buf } } @@ -876,12 +876,12 @@ mod test { } } - pub(crate) fn buf(&self) -> io::Result>> { + pub(crate) fn buf(&self) -> io::Result>> { self.buf.try_lock().map_err(Self::map_error) } } - impl<'a> io::Write for MockWriter<'a> { + impl io::Write for MockWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.buf()?.write(buf) } @@ -891,21 +891,26 @@ mod test { } } - pub(crate) struct MockMakeWriter<'a> { - buf: &'a Mutex>, + #[derive(Clone, Default)] + pub(crate) struct MockMakeWriter { + buf: Arc>>, } - impl<'a> MockMakeWriter<'a> { - pub(crate) fn new(buf: &'a Mutex>) -> Self { + impl MockMakeWriter { + pub(crate) fn new(buf: Arc>>) -> Self { Self { buf } } + + pub(crate) fn buf(&self) -> MutexGuard<'_, Vec> { + self.buf.lock().unwrap() + } } - impl<'a> MakeWriter for MockMakeWriter<'a> { - type Writer = MockWriter<'a>; + impl<'a> MakeWriter<'a> for MockMakeWriter { + type Writer = MockWriter; - fn make_writer(&self) -> Self::Writer { - MockWriter::new(self.buf) + fn make_writer(&'a self) -> Self::Writer { + MockWriter::new(self.buf.clone()) } } diff --git a/tracing-subscriber/src/fmt/writer.rs b/tracing-subscriber/src/fmt/writer.rs index 303d6f0bf5..fa0782b3f9 100644 --- a/tracing-subscriber/src/fmt/writer.rs +++ b/tracing-subscriber/src/fmt/writer.rs @@ -1,23 +1,94 @@ //! Abstractions for creating [`io::Write`] instances. //! //! [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html - -use std::io; +use std::{ + io, + sync::{Mutex, MutexGuard}, +}; /// A type that can create [`io::Write`] instances. /// -/// `MakeWriter` is used by [`FmtSubscriber`] to print formatted text representations of -/// [`Event`]s. +/// `MakeWriter` is used by [`FmtSubscriber`] 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`][mutex] when the tyoe 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() +/// } /// -/// 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`]. +/// 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. For example, this will send every 5th event to stderr, and all +/// other events to stdout (why you would actually want to do this, I have no +/// idea, but you _can_): +/// +/// ``` +/// use std::io; +/// use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +/// +/// let n = AtomicUsize::new(0); +/// let subscriber = tracing_subscriber::fmt() +/// .with_writer(move || -> Box { +/// 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`][mutex]. For example, we could +/// write to a file like so: +/// +/// ``` +/// use std::{fs::File, sync::Mutex}; +/// +/// # fn docs() -> Result<(), Box> { +/// 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`]: https://doc.rust-lang.org/std/io/trait.Write.html /// [`FmtSubscriber`]: ../struct.Subscriber.html /// [`Event`]: https://docs.rs/tracing-core/0.1.5/tracing_core/event/struct.Event.html /// [`io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html /// [`io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html -pub trait MakeWriter { +/// [mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +pub trait MakeWriter<'a> { /// The concrete [`io::Write`] implementation returned by [`make_writer`]. /// /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html @@ -37,35 +108,91 @@ pub trait MakeWriter { /// [`FmtSubscriber`]: ../struct.Subscriber.html /// [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html /// [`MakeWriter`]: trait.MakeWriter.html - fn make_writer(&self) -> Self::Writer; + fn make_writer(&'a self) -> Self::Writer; } -impl MakeWriter for F +/// A type implementing [`io::Write`] for a [`MutexGuard`] where tyhe 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`]: https://doc.rust-lang.org/std/io/trait.Write.html +/// [`MutexGuard`]: https://doc.rust-lang.org/std/sync/struct.MutexGuard.html +/// [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +/// [`MakeWriter`]: trait.MakeWriter.html +#[derive(Debug)] +pub struct MutexGuardWriter<'a, W>(MutexGuard<'a, W>); + +impl<'a, F, W> MakeWriter<'a> for F where F: Fn() -> W, W: io::Write, { type Writer = W; - fn make_writer(&self) -> Self::Writer { + fn make_writer(&'a self) -> Self::Writer { (self)() } } +impl<'a, W> MakeWriter<'a> for Mutex +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 { + 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 { + 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) + } +} + #[cfg(test)] mod test { use super::MakeWriter; use crate::fmt::format::Format; use crate::fmt::test::{MockMakeWriter, MockWriter}; use crate::fmt::Subscriber; - use lazy_static::lazy_static; - use std::sync::Mutex; + use std::sync::{Arc, Mutex}; use tracing::error; use tracing_core::dispatcher::{self, Dispatch}; fn test_writer(make_writer: T, msg: &str, buf: &Mutex>) where - T: MakeWriter + Send + Sync + 'static, + T: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, { let subscriber = { #[cfg(feature = "ansi")] @@ -98,23 +225,27 @@ mod test { #[test] fn custom_writer_closure() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = || MockWriter::new(&BUF); + 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_writer(make_writer, msg, &buf); } #[test] fn custom_writer_struct() { - lazy_static! { - static ref BUF: Mutex> = Mutex::new(vec![]); - } - - let make_writer = MockMakeWriter::new(&BUF); + 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_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); } }