Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add support for chrono #18

Merged
merged 10 commits into from Dec 15, 2023
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