Skip to content

Commit c6d96fd

Browse files
[tracing-subscriber]: add chrono crate implementations of FormatTime (#2690)
Issue #2080 explains that it's not possible to soundly use [`tracing_subscriber::fmt::time::LocalTime`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/time/struct.LocalTime.html) in a multithreaded context. It proposes adding alternative time formatters that use the [chrono crate](https://docs.rs/chrono/latest/chrono/) to workaround which is what this PR offers. A new source file 'chrono_crate.rs' is added to the 'tracing-subscriber' package implementing `mod chrono_crate` providing two new tag types `LocalTime` and `Utc` with associated `time::FormatTime` trait implementations that call `chrono::Local::now().to_rfc3339()` and `chrono::Utc::now().to_rfc3339()` respectively. Simple unit-tests of the new functionality accompany the additions. --------- Co-authored-by: David Barsky <[email protected]> Co-authored-by: Shayne Fletcher <[email protected]>
1 parent e10ef68 commit c6d96fd

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

tracing-subscriber/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ tracing-serde = { path = "../tracing-serde", version = "0.1.3", optional = true
5959

6060
# opt-in deps
6161
parking_lot = { version = "0.12.1", optional = true }
62+
chrono = { version = "0.4.26", default-features = false, features = ["clock", "std"], optional = true }
6263

6364
# registry
6465
sharded-slab = { version = "0.1.4", optional = true }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use crate::fmt::format::Writer;
2+
use crate::fmt::time::FormatTime;
3+
4+
use std::sync::Arc;
5+
6+
/// Formats [local time]s and [UTC time]s with `FormatTime` implementations
7+
/// that use the [`chrono` crate].
8+
///
9+
/// [local time]: [`chrono::offset::Local`]
10+
/// [UTC time]: [`chrono::offset::Utc`]
11+
/// [`chrono` crate]: [`chrono`]
12+
13+
/// Formats the current [local time] using a [formatter] from the [`chrono`] crate.
14+
///
15+
/// [local time]: chrono::Local::now()
16+
/// [formatter]: chrono::format
17+
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
18+
#[derive(Debug, Clone, Eq, PartialEq, Default)]
19+
pub struct ChronoLocal {
20+
format: Arc<ChronoFmtType>,
21+
}
22+
23+
impl ChronoLocal {
24+
/// Format the time using the [`RFC 3339`] format
25+
/// (a subset of [`ISO 8601`]).
26+
///
27+
/// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
28+
/// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
29+
pub fn rfc_3339() -> Self {
30+
Self {
31+
format: Arc::new(ChronoFmtType::Rfc3339),
32+
}
33+
}
34+
35+
/// Format the time using the given format string.
36+
///
37+
/// See [`chrono::format::strftime`] for details on the supported syntax.
38+
pub fn new(format_string: String) -> Self {
39+
Self {
40+
format: Arc::new(ChronoFmtType::Custom(format_string)),
41+
}
42+
}
43+
}
44+
45+
impl FormatTime for ChronoLocal {
46+
fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
47+
let t = chrono::Local::now();
48+
match self.format.as_ref() {
49+
ChronoFmtType::Rfc3339 => {
50+
use chrono::format::{Fixed, Item};
51+
write!(
52+
w,
53+
"{}",
54+
t.format_with_items(core::iter::once(Item::Fixed(Fixed::RFC3339)))
55+
)
56+
}
57+
ChronoFmtType::Custom(fmt) => {
58+
write!(w, "{}", t.format(fmt))
59+
}
60+
}
61+
}
62+
}
63+
64+
/// Formats the current [UTC time] using a [formatter] from the [`chrono`] crate.
65+
///
66+
/// [UTC time]: chrono::Utc::now()
67+
/// [formatter]: chrono::format
68+
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
69+
#[derive(Debug, Clone, Eq, PartialEq, Default)]
70+
pub struct ChronoUtc {
71+
format: Arc<ChronoFmtType>,
72+
}
73+
74+
impl ChronoUtc {
75+
/// Format the time using the [`RFC 3339`] format
76+
/// (a subset of [`ISO 8601`]).
77+
///
78+
/// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
79+
/// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
80+
pub fn rfc_3339() -> Self {
81+
Self {
82+
format: Arc::new(ChronoFmtType::Rfc3339),
83+
}
84+
}
85+
86+
/// Format the time using the given format string.
87+
///
88+
/// See [`chrono::format::strftime`] for details on the supported syntax.
89+
pub fn new(format_string: String) -> Self {
90+
Self {
91+
format: Arc::new(ChronoFmtType::Custom(format_string)),
92+
}
93+
}
94+
}
95+
96+
impl FormatTime for ChronoUtc {
97+
fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
98+
let t = chrono::Utc::now();
99+
match self.format.as_ref() {
100+
ChronoFmtType::Rfc3339 => w.write_str(&t.to_rfc3339()),
101+
ChronoFmtType::Custom(fmt) => w.write_str(&format!("{}", t.format(fmt))),
102+
}
103+
}
104+
}
105+
106+
/// The RFC 3339 format is used by default but a custom format string
107+
/// can be used. See [`chrono::format::strftime`]for details on
108+
/// the supported syntax.
109+
///
110+
/// [`chrono::format::strftime`]: https://docs.rs/chrono/0.4.9/chrono/format/strftime/index.html
111+
#[derive(Debug, Clone, Eq, PartialEq)]
112+
enum ChronoFmtType {
113+
/// Format according to the RFC 3339 convention.
114+
Rfc3339,
115+
/// Format according to a custom format string.
116+
Custom(String),
117+
}
118+
119+
impl Default for ChronoFmtType {
120+
fn default() -> Self {
121+
ChronoFmtType::Rfc3339
122+
}
123+
}
124+
125+
#[cfg(test)]
126+
mod tests {
127+
use crate::fmt::format::Writer;
128+
use crate::fmt::time::FormatTime;
129+
130+
use std::sync::Arc;
131+
132+
use super::ChronoFmtType;
133+
use super::ChronoLocal;
134+
use super::ChronoUtc;
135+
136+
#[test]
137+
fn test_chrono_format_time_utc_default() {
138+
let mut buf = String::new();
139+
let mut dst: Writer<'_> = Writer::new(&mut buf);
140+
assert!(FormatTime::format_time(&ChronoUtc::default(), &mut dst).is_ok());
141+
// e.g. `buf` contains "2023-08-18T19:05:08.662499+00:00"
142+
assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
143+
}
144+
145+
#[test]
146+
fn test_chrono_format_time_utc_custom() {
147+
let fmt = ChronoUtc {
148+
format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
149+
};
150+
let mut buf = String::new();
151+
let mut dst: Writer<'_> = Writer::new(&mut buf);
152+
assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
153+
// e.g. `buf` contains "Wed Aug 23 15:53:23 2023"
154+
assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
155+
}
156+
157+
#[test]
158+
fn test_chrono_format_time_local_default() {
159+
let mut buf = String::new();
160+
let mut dst: Writer<'_> = Writer::new(&mut buf);
161+
assert!(FormatTime::format_time(&ChronoLocal::default(), &mut dst).is_ok());
162+
// e.g. `buf` contains "2023-08-18T14:59:08.662499-04:00".
163+
assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
164+
}
165+
166+
#[test]
167+
fn test_chrono_format_time_local_custom() {
168+
let fmt = ChronoLocal {
169+
format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
170+
};
171+
let mut buf = String::new();
172+
let mut dst: Writer<'_> = Writer::new(&mut buf);
173+
assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
174+
// e.g. `buf` contains "Wed Aug 23 15:55:46 2023".
175+
assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
176+
}
177+
}

tracing-subscriber/src/fmt/time/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod datetime;
77

88
#[cfg(feature = "time")]
99
mod time_crate;
10+
1011
#[cfg(feature = "time")]
1112
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
1213
pub use time_crate::UtcTime;
@@ -19,6 +20,18 @@ pub use time_crate::LocalTime;
1920
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
2021
pub use time_crate::OffsetTime;
2122

23+
/// [`chrono`]-based implementation for [`FormatTime`].
24+
#[cfg(feature = "chrono")]
25+
mod chrono_crate;
26+
27+
#[cfg(feature = "chrono")]
28+
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
29+
pub use chrono_crate::ChronoLocal;
30+
31+
#[cfg(feature = "chrono")]
32+
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
33+
pub use chrono_crate::ChronoUtc;
34+
2235
/// A type that can measure and format the current time.
2336
///
2437
/// This trait is used by `Format` to include a timestamp with each `Event` when it is logged.

0 commit comments

Comments
 (0)