Skip to content

Commit

Permalink
feature: add support for chrono (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbarsky committed Dec 15, 2023
1 parent 0eaa2e2 commit 8a993c2
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 136 deletions.
34 changes: 25 additions & 9 deletions .github/workflows/ci.yml
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -61,4 +61,20 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
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
15 changes: 11 additions & 4 deletions 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"
Expand All @@ -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"
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions 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> {
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions 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() {
Expand Down Expand Up @@ -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())
Expand Down
161 changes: 42 additions & 119 deletions 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<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(())
}
}

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,
Expand Down Expand Up @@ -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<F = Vec<FormatItem<'static>>> {
format: F,
pub struct UtcTime {
time: ChronoUtc,
}

impl<F> FormatTime for UtcTime<F>
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<FormatItem> = 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.
///
/// <div class="example-wrap" style="display:inline-block">
/// <pre class="compile_fail" style="white-space:normal;font:inherit;">
/// <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/"><code>time</code>
/// crate</a> must be compiled with <code>--cfg unsound_local_offset</code> 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 <a href="https://docs.rs/time/0.3.4/time/#feature-flags"><code>time</code>
/// documentation</a> for more details.
/// </pre></div>
/// 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<F = Vec<FormatItem<'static>>> {
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<FormatItem> = time::format_description::parse(
"[month][day] [hour]:[minute]:[second].[subsecond digits:6]",
)
.expect("Unable to make time formatter");
Self { format }
}
}

impl<F> FormatTime for LocalTime<F>
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> {
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Expand Up @@ -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.
Expand Down

0 comments on commit 8a993c2

Please sign in to comment.