diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51e280c..42116de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,8 @@ jobs: name: Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable with: profile: minimal toolchain: stable @@ -21,8 +21,8 @@ jobs: name: Test Suite runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable with: profile: minimal toolchain: stable @@ -35,8 +35,8 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable with: profile: minimal toolchain: stable @@ -51,8 +51,8 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable with: profile: minimal toolchain: stable @@ -61,4 +61,20 @@ jobs: - uses: actions-rs/cargo@v1 with: command: clippy - args: -- -D warnings \ No newline at end of file + args: -- -D warnings + + cargo-hack: + needs: check + name: cargo check (feature combinations) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + profile: minimal + toolchain: stable + override: true + - uses: taiki-e/install-action@cargo-hack + with: + command: cargo + args: hack check --feature-powerset --no-dev-deps \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0d77e3e..ab49884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tracing-glog" -version = "0.3.0" +version = "0.4.0" edition = "2021" description = "a glog-inspired formatter for tracing-subscriber" license = "MIT OR Apache-2.0" @@ -11,10 +11,11 @@ documentation = "https://docs.rs/tracing-glog" [dependencies] tracing = { version = "0.1", default-features = false } -tracing-subscriber = { version = "0.3.3", features = ["std", "fmt", "registry", "time", "local-time"], default-features = false } -time = { version = "0.3.9", features = ["formatting"] } +tracing-subscriber = { version = "0.3.18", features = ["std", "fmt", "registry", "chrono"], default-features = false } +chrono = { version = "0.4.20" } +time = { version = "0.3.9", features = ["formatting"], default-features = false, optional = true } nu-ansi-term = { version = "0.46", optional = true } -tracing-log = { version = "0.1", optional = true } +tracing-log = { version = "0.2", default-features = false, optional = true } [dev-dependencies] thiserror = "1" @@ -27,6 +28,12 @@ tokio = { version = "1.21", features = ["full"] } default = ["ansi"] ansi = ["nu-ansi-term", "tracing-subscriber/ansi"] tracing-log = ["dep:tracing-log"] +time = ["dep:time", "tracing-subscriber/time"] +local-time = ["dep:time", "tracing-subscriber/local-time"] + +[[example]] +name = "tokio" +required-features = ["ansi"] [package.metadata.docs.rs] all-features = true diff --git a/examples/tokio.rs b/examples/tokio.rs index 1b9251a..49dd15f 100644 --- a/examples/tokio.rs +++ b/examples/tokio.rs @@ -1,7 +1,7 @@ use anyhow::Error; use tokio::task::JoinSet; use tracing::{debug, info, instrument, span, Instrument as _, Level}; -use tracing_glog::{Glog, GlogFields, UtcTime}; +use tracing_glog::{Glog, GlogFields, LocalTime}; #[instrument] async fn parent_task(subtasks: usize) -> Result<(), Error> { @@ -36,7 +36,7 @@ async fn main() -> Result<(), Error> { Glog::default() .with_target(false) .with_thread_names(false) - .with_timer(UtcTime::default()), + .with_timer(LocalTime::default()), ) .fmt_fields(GlogFields::default()) .init(); diff --git a/examples/tokio_compact.rs b/examples/tokio_compact.rs index 7b8c3ea..091e6aa 100644 --- a/examples/tokio_compact.rs +++ b/examples/tokio_compact.rs @@ -1,7 +1,7 @@ use anyhow::Error; use tokio::task::JoinSet; use tracing::{debug, info, instrument, span, Instrument as _, Level}; -use tracing_glog::{Glog, GlogFields, UtcTime}; +use tracing_glog::{Glog, GlogFields, LocalTime}; #[instrument(skip_all)] async fn no_fields() { @@ -47,7 +47,7 @@ async fn main() -> Result<(), Error> { Glog::default() .with_target(false) .with_thread_names(false) - .with_timer(UtcTime::default()) + .with_timer(LocalTime::default()) .with_span_names(false), ) .fmt_fields(GlogFields::default().compact()) diff --git a/src/format.rs b/src/format.rs index 7868027..a7f6735 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,49 +1,10 @@ #[cfg(feature = "ansi")] -use crate::nu_ansi_term::{Color, Style}; -use std::{fmt, io}; -use time::{format_description::FormatItem, formatting::Formattable, OffsetDateTime}; +use nu_ansi_term::{Color, Style}; +use std::fmt; use tracing::{Level, Metadata}; use tracing_subscriber::fmt::{format::Writer, time::FormatTime}; -/// 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`. -pub(crate) struct WriteAdaptor<'a> { - fmt_write: &'a mut dyn fmt::Write, -} - -impl<'a> WriteAdaptor<'a> { - pub(crate) fn new(fmt_write: &'a mut dyn fmt::Write) -> Self { - Self { fmt_write } - } -} - -impl<'a> io::Write for WriteAdaptor<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - 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(()) - } -} - -impl<'a> fmt::Debug for WriteAdaptor<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("WriteAdaptor { .. }") - } -} +use tracing_subscriber::fmt::time::{ChronoLocal, ChronoUtc}; pub(crate) struct FmtLevel { pub level: Level, @@ -91,113 +52,75 @@ impl fmt::Display for FmtLevel { } } -/// Formats the current [UTC time] using a [formatter] from the [`time` crate]. +/// Formats the current [UTC time] using [`chrono` crate]. /// -/// To format the current [local time] instead, use the [`LocalTime`] type. +/// To format the current local time instead, use the [`LocalTime`] +/// or the [`LocalTime`] type. /// -/// [UTC time]: time::OffsetDateTime::now_utc -/// [formatter]: time::formatting::Formattable -/// [`time` crate]: time -/// [local time]: time::OffsetDateTime::now_local +/// [UTC time]: ChronoUtc +/// [`chrono` crate]: chrono #[derive(Clone, Debug)] -pub struct UtcTime>> { - format: F, +pub struct UtcTime { + time: ChronoUtc, } -impl FormatTime for UtcTime -where - F: Formattable, -{ - fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result { - let now = OffsetDateTime::now_utc(); - +impl FormatTime for UtcTime { + fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result { #[cfg(feature = "ansi")] - if writer.has_ansi_escapes() { + if w.has_ansi_escapes() { let style = Style::new().dimmed(); - write!(writer, "{}", style.prefix())?; - format_datetime(writer, now, &self.format)?; - write!(writer, "{}", style.suffix())?; + write!(w, "{}", style.prefix())?; + self.time.format_time(w)?; + write!(w, "{}", style.suffix())?; return Ok(()); } - format_datetime(writer, now, &self.format) + self.time.format_time(w) } } impl Default for UtcTime { fn default() -> Self { - let format: Vec = time::format_description::parse( - "[month][day] [hour]:[minute]:[second].[subsecond digits:6]", - ) - .expect("Unable to make time formatter"); - Self { format } + let fmt_string = String::from("%m%d %H:%M:%S%.6f"); + Self { + time: ChronoUtc::new(fmt_string), + } } } -/// Formats the current [local time] using a [formatter] from the [`time` crate]. -/// -/// To format the current [UTC time] instead, use the [`UtcTime`] type. -/// -///
-///
-///     Warning: The time
-///     crate must be compiled with --cfg unsound_local_offset in order to use
-///     local timestamps. When this cfg is not enabled, local timestamps cannot be recorded, and
-///     events will be logged without timestamps.
+/// Formats the current [`local time`] using [`chrono` crate].
 ///
-///    See the time
-///    documentation for more details.
-/// 
+/// To format the UTC time instead, use the [`UtcTime`] +/// or the [`crate::time_crate::UtcTime`] type. /// -/// [local time]: time::OffsetDateTime::now_local -/// [formatter]: time::formatting::Formattable -/// [`time` crate]: time -/// [UTC time]: time::OffsetDateTime::now_utc -#[derive(Clone, Debug)] -pub struct LocalTime>> { - format: F, +/// [`local time`]: tracing_subscriber::fmt::time::ChronoLocal +/// [`chrono` crate]: chrono +pub struct LocalTime { + time: ChronoLocal, } -impl Default for LocalTime { - fn default() -> Self { - let format: Vec = time::format_description::parse( - "[month][day] [hour]:[minute]:[second].[subsecond digits:6]", - ) - .expect("Unable to make time formatter"); - Self { format } - } -} - -impl FormatTime for LocalTime -where - F: Formattable, -{ - fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result { - let now = OffsetDateTime::now_local().map_err(|_| fmt::Error)?; - +impl FormatTime for LocalTime { + fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result { #[cfg(feature = "ansi")] - if writer.has_ansi_escapes() { + if w.has_ansi_escapes() { let style = Style::new().dimmed(); - write!(writer, "{}", style.prefix())?; - format_datetime(writer, now, &self.format)?; - // necessary to provide space between the time and the PID - write!(writer, "{}", style.suffix())?; + write!(w, "{}", style.prefix())?; + self.time.format_time(w)?; + write!(w, "{}", style.suffix())?; return Ok(()); } - format_datetime(writer, now, &self.format) + self.time.format_time(w) } } -fn format_datetime( - into: &mut Writer<'_>, - now: OffsetDateTime, - fmt: &impl Formattable, -) -> fmt::Result { - let mut into = WriteAdaptor::new(into); - now.format_into(&mut into, fmt) - .map_err(|_| fmt::Error) - .map(|_| ()) +impl Default for LocalTime { + fn default() -> Self { + let fmt_string = String::from("%m%d %H:%M:%S%.6f"); + Self { + time: ChronoLocal::new(fmt_string), + } + } } pub(crate) struct FormatProcessData<'a> { diff --git a/src/lib.rs b/src/lib.rs index f8309a9..cefd889 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,10 +93,14 @@ #[deny(rustdoc::broken_intra_doc_links)] mod format; +#[cfg(feature = "time")] +pub mod time_crate; + #[cfg(feature = "ansi")] mod nu_ansi_term { pub use ::nu_ansi_term::*; } + #[cfg(not(feature = "ansi"))] mod nu_ansi_term { // Minimal API shim for nu_ansi_term to avoid a pile of #[cfg(feature = "ansi")] directives. diff --git a/src/time_crate.rs b/src/time_crate.rs new file mode 100644 index 0000000..91df3fb --- /dev/null +++ b/src/time_crate.rs @@ -0,0 +1,158 @@ +#[cfg(feature = "ansi")] +use crate::nu_ansi_term::Style; +use std::{fmt, io}; +use time::{format_description::FormatItem, formatting::Formattable, OffsetDateTime}; +use tracing_subscriber::fmt::{format::Writer, time::FormatTime}; + +/// 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`. +pub(crate) struct WriteAdaptor<'a> { + fmt_write: &'a mut dyn fmt::Write, +} + +#[cfg(feature = "time")] +fn format_datetime( + into: &mut Writer<'_>, + now: OffsetDateTime, + fmt: &impl Formattable, +) -> fmt::Result { + let mut into = WriteAdaptor::new(into); + now.format_into(&mut into, fmt) + .map_err(|_| fmt::Error) + .map(|_| ()) +} + +impl<'a> WriteAdaptor<'a> { + pub(crate) fn new(fmt_write: &'a mut dyn fmt::Write) -> Self { + Self { fmt_write } + } +} + +impl<'a> std::io::Write for WriteAdaptor<'a> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let s = std::str::from_utf8(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + + self.fmt_write + .write_str(s) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + Ok(s.as_bytes().len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl<'a> fmt::Debug for WriteAdaptor<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("WriteAdaptor { .. }") + } +} + +/// Formats the current [UTC time] using a [formatter] from the [`time` crate]. +/// +/// To format the current [local time] instead, use the [`LocalTime`] type. +/// +/// [UTC time]: time::OffsetDateTime::now_utc +/// [formatter]: time::formatting::Formattable +/// [`time` crate]: time +/// [local time]: time::OffsetDateTime::now_local +#[derive(Clone, Debug)] +pub struct UtcTime>> { + format: F, +} + +impl FormatTime for UtcTime +where + F: Formattable, +{ + fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result { + let now = OffsetDateTime::now_utc(); + + #[cfg(feature = "ansi")] + if writer.has_ansi_escapes() { + let style = Style::new().dimmed(); + write!(writer, "{}", style.prefix())?; + format_datetime(writer, now, &self.format)?; + write!(writer, "{}", style.suffix())?; + return Ok(()); + } + + format_datetime(writer, now, &self.format) + } +} + +impl Default for UtcTime { + fn default() -> Self { + let format: Vec = time::format_description::parse( + "[month][day] [hour]:[minute]:[second].[subsecond digits:6]", + ) + .expect("Unable to make time formatter"); + Self { format } + } +} + +/// Formats the current [local time] using a [formatter] from the [`time` crate]. +/// +/// To format the current [UTC time] instead, use the [`UtcTime`] type. +/// +///
+///
+///     Warning: The time
+///     crate must be compiled with --cfg unsound_local_offset in order to use
+///     local timestamps. When this cfg is not enabled, local timestamps cannot be recorded, and
+///     events will be logged without timestamps.
+///
+///    See the time
+///    documentation for more details.
+/// 
+/// +/// [local time]: time::OffsetDateTime::now_local +/// [formatter]: time::formatting::Formattable +/// [`time` crate]: time +/// [UTC time]: time::OffsetDateTime::now_utc +#[derive(Clone, Debug)] +#[cfg(feature = "local-time")] +pub struct LocalTime>> { + format: F, +} + +#[cfg(feature = "local-time")] +impl Default for LocalTime { + fn default() -> Self { + let format: Vec = time::format_description::parse( + "[month][day] [hour]:[minute]:[second].[subsecond digits:6]", + ) + .expect("Unable to make time formatter"); + Self { format } + } +} + +#[cfg(feature = "local-time")] +impl FormatTime for LocalTime +where + F: Formattable, +{ + fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result { + let now = OffsetDateTime::now_local().map_err(|_| fmt::Error)?; + + #[cfg(feature = "ansi")] + if writer.has_ansi_escapes() { + let style = Style::new().dimmed(); + write!(writer, "{}", style.prefix())?; + format_datetime(writer, now, &self.format)?; + // necessary to provide space between the time and the PID + write!(writer, "{}", style.suffix())?; + return Ok(()); + } + + format_datetime(writer, now, &self.format) + } +}