tracing_subscriber/fmt/time/
time_crate.rs

1use crate::fmt::{format::Writer, time::FormatTime, writer::WriteAdaptor};
2use std::fmt;
3use time::{format_description::well_known, formatting::Formattable, OffsetDateTime, UtcOffset};
4
5/// Formats the current [local time] using a [formatter] from the [`time` crate].
6///
7/// To format the current [UTC time] instead, use the [`UtcTime`] type.
8///
9/// <div class="example-wrap" style="display:inline-block">
10/// <pre class="compile_fail" style="white-space:normal;font:inherit;">
11///     <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/"><code>time</code>
12///     crate</a> must be compiled with <code>--cfg unsound_local_offset</code> in order to use
13///     local timestamps. When this cfg is not enabled, local timestamps cannot be recorded, and
14///     events will be logged without timestamps.
15///
16///    Alternatively, [`OffsetTime`] can log with a local offset if it is initialized early.
17///
18///    See the <a href="https://docs.rs/time/0.3.4/time/#feature-flags"><code>time</code>
19///    documentation</a> for more details.
20/// </pre></div>
21///
22/// [local time]: time::OffsetDateTime::now_local
23/// [UTC time]:     time::OffsetDateTime::now_utc
24/// [formatter]:    time::formatting::Formattable
25/// [`time` crate]: time
26#[derive(Clone, Debug)]
27#[cfg_attr(
28    docsrs,
29    doc(cfg(all(unsound_local_offset, feature = "time", feature = "local-time")))
30)]
31#[cfg(feature = "local-time")]
32pub struct LocalTime<F> {
33    format: F,
34}
35
36/// Formats the current [UTC time] using a [formatter] from the [`time` crate].
37///
38/// To format the current [local time] instead, use the [`LocalTime`] type.
39///
40/// [local time]: time::OffsetDateTime::now_local
41/// [UTC time]:     time::OffsetDateTime::now_utc
42/// [formatter]:    time::formatting::Formattable
43/// [`time` crate]: time
44#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
45#[derive(Clone, Debug)]
46pub struct UtcTime<F> {
47    format: F,
48}
49
50/// Formats the current time using a fixed offset and a [formatter] from the [`time` crate].
51///
52/// This is typically used as an alternative to [`LocalTime`]. `LocalTime` determines the offset
53/// every time it formats a message, which may be unsound or fail. With `OffsetTime`, the offset is
54/// determined once. This makes it possible to do so while the program is still single-threaded and
55/// handle any errors. However, this also means the offset cannot change while the program is
56/// running (the offset will not change across DST changes).
57///
58/// [formatter]: time::formatting::Formattable
59/// [`time` crate]: time
60#[derive(Clone, Debug)]
61#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
62pub struct OffsetTime<F> {
63    offset: time::UtcOffset,
64    format: F,
65}
66
67// === impl LocalTime ===
68
69#[cfg(feature = "local-time")]
70impl LocalTime<well_known::Rfc3339> {
71    /// Returns a formatter that formats the current [local time] in the
72    /// [RFC 3339] format (a subset of the [ISO 8601] timestamp format).
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use tracing_subscriber::fmt::{self, time};
78    ///
79    /// let subscriber = tracing_subscriber::fmt()
80    ///     .with_timer(time::LocalTime::rfc_3339());
81    /// # drop(subscriber);
82    /// ```
83    ///
84    /// [local time]: time::OffsetDateTime::now_local
85    /// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
86    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
87    pub fn rfc_3339() -> Self {
88        Self::new(well_known::Rfc3339)
89    }
90}
91
92#[cfg(feature = "local-time")]
93impl<F: Formattable> LocalTime<F> {
94    /// Returns a formatter that formats the current [local time] using the
95    /// [`time` crate] with the provided provided format. The format may be any
96    /// type that implements the [`Formattable`] trait.
97    ///
98    ///
99    /// <div class="example-wrap" style="display:inline-block">
100    /// <pre class="compile_fail" style="white-space:normal;font:inherit;">
101    ///     <strong>Warning</strong>: The <a href = "https://docs.rs/time/0.3/time/">
102    ///     <code>time</code> crate</a> must be compiled with <code>--cfg
103    ///     unsound_local_offset</code> in order to use local timestamps. When this
104    ///     cfg is not enabled, local timestamps cannot be recorded, and
105    ///     events will be logged without timestamps.
106    ///
107    ///    See the <a href="https://docs.rs/time/0.3.4/time/#feature-flags">
108    ///    <code>time</code> documentation</a> for more details.
109    /// </pre></div>
110    ///
111    /// Typically, the format will be a format description string, or one of the
112    /// `time` crate's [well-known formats].
113    ///
114    /// If the format description is statically known, then the
115    /// [`format_description!`] macro should be used. This is identical to the
116    /// [`time::format_description::parse`] method, but runs at compile-time,
117    /// throwing an error if the format description is invalid. If the desired format
118    /// is not known statically (e.g., a user is providing a format string), then the
119    /// [`time::format_description::parse`] method should be used. Note that this
120    /// method is fallible.
121    ///
122    /// See the [`time` book] for details on the format description syntax.
123    ///
124    /// # Examples
125    ///
126    /// Using the [`format_description!`] macro:
127    ///
128    /// ```
129    /// use tracing_subscriber::fmt::{self, time::LocalTime};
130    /// use time::macros::format_description;
131    ///
132    /// let timer = LocalTime::new(format_description!("[hour]:[minute]:[second]"));
133    /// let subscriber = tracing_subscriber::fmt()
134    ///     .with_timer(timer);
135    /// # drop(subscriber);
136    /// ```
137    ///
138    /// Using [`time::format_description::parse`]:
139    ///
140    /// ```
141    /// use tracing_subscriber::fmt::{self, time::LocalTime};
142    ///
143    /// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
144    ///     .expect("format string should be valid!");
145    /// let timer = LocalTime::new(time_format);
146    /// let subscriber = tracing_subscriber::fmt()
147    ///     .with_timer(timer);
148    /// # drop(subscriber);
149    /// ```
150    ///
151    /// Using the [`format_description!`] macro requires enabling the `time`
152    /// crate's "macros" feature flag.
153    ///
154    /// Using a [well-known format][well-known formats] (this is equivalent to
155    /// [`LocalTime::rfc_3339`]):
156    ///
157    /// ```
158    /// use tracing_subscriber::fmt::{self, time::LocalTime};
159    ///
160    /// let timer = LocalTime::new(time::format_description::well_known::Rfc3339);
161    /// let subscriber = tracing_subscriber::fmt()
162    ///     .with_timer(timer);
163    /// # drop(subscriber);
164    /// ```
165    ///
166    /// [local time]: time::OffsetDateTime::now_local()
167    /// [`time` crate]: time
168    /// [`Formattable`]: time::formatting::Formattable
169    /// [well-known formats]: time::format_description::well_known
170    /// [`format_description!`]: time::macros::format_description!
171    /// [`time::format_description::parse`]: time::format_description::parse()
172    /// [`time` book]: https://time-rs.github.io/book/api/format-description.html
173    pub fn new(format: F) -> Self {
174        Self { format }
175    }
176}
177
178#[cfg(feature = "local-time")]
179impl<F> FormatTime for LocalTime<F>
180where
181    F: Formattable,
182{
183    fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
184        let now = OffsetDateTime::now_local().map_err(|_| fmt::Error)?;
185        format_datetime(now, w, &self.format)
186    }
187}
188
189#[cfg(feature = "local-time")]
190impl<F> Default for LocalTime<F>
191where
192    F: Formattable + Default,
193{
194    fn default() -> Self {
195        Self::new(F::default())
196    }
197}
198
199// === impl UtcTime ===
200
201impl UtcTime<well_known::Rfc3339> {
202    /// Returns a formatter that formats the current [UTC time] in the
203    /// [RFC 3339] format, which is a subset of the [ISO 8601] timestamp format.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use tracing_subscriber::fmt::{self, time};
209    ///
210    /// let subscriber = tracing_subscriber::fmt()
211    ///     .with_timer(time::UtcTime::rfc_3339());
212    /// # drop(subscriber);
213    /// ```
214    ///
215    /// [local time]: time::OffsetDateTime::now_utc
216    /// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
217    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
218    pub fn rfc_3339() -> Self {
219        Self::new(well_known::Rfc3339)
220    }
221}
222
223impl<F: Formattable> UtcTime<F> {
224    /// Returns a formatter that formats the current [UTC time] using the
225    /// [`time` crate], with the provided provided format. The format may be any
226    /// type that implements the [`Formattable`] trait.
227    ///
228    /// Typically, the format will be a format description string, or one of the
229    /// `time` crate's [well-known formats].
230    ///
231    /// If the format description is statically known, then the
232    /// [`format_description!`] macro should be used. This is identical to the
233    /// [`time::format_description::parse`] method, but runs at compile-time,
234    /// failing  an error if the format description is invalid. If the desired format
235    /// is not known statically (e.g., a user is providing a format string), then the
236    /// [`time::format_description::parse`] method should be used. Note that this
237    /// method is fallible.
238    ///
239    /// See the [`time` book] for details on the format description syntax.
240    ///
241    /// # Examples
242    ///
243    /// Using the [`format_description!`] macro:
244    ///
245    /// ```
246    /// use tracing_subscriber::fmt::{self, time::UtcTime};
247    /// use time::macros::format_description;
248    ///
249    /// let timer = UtcTime::new(format_description!("[hour]:[minute]:[second]"));
250    /// let subscriber = tracing_subscriber::fmt()
251    ///     .with_timer(timer);
252    /// # drop(subscriber);
253    /// ```
254    ///
255    /// Using the [`format_description!`] macro requires enabling the `time`
256    /// crate's "macros" feature flag.
257    ///
258    /// Using [`time::format_description::parse`]:
259    ///
260    /// ```
261    /// use tracing_subscriber::fmt::{self, time::UtcTime};
262    ///
263    /// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
264    ///     .expect("format string should be valid!");
265    /// let timer = UtcTime::new(time_format);
266    /// let subscriber = tracing_subscriber::fmt()
267    ///     .with_timer(timer);
268    /// # drop(subscriber);
269    /// ```
270    ///
271    /// Using a [well-known format][well-known formats] (this is equivalent to
272    /// [`UtcTime::rfc_3339`]):
273    ///
274    /// ```
275    /// use tracing_subscriber::fmt::{self, time::UtcTime};
276    ///
277    /// let timer = UtcTime::new(time::format_description::well_known::Rfc3339);
278    /// let subscriber = tracing_subscriber::fmt()
279    ///     .with_timer(timer);
280    /// # drop(subscriber);
281    /// ```
282    ///
283    /// [UTC time]: time::OffsetDateTime::now_utc()
284    /// [`time` crate]: time
285    /// [`Formattable`]: time::formatting::Formattable
286    /// [well-known formats]: time::format_description::well_known
287    /// [`format_description!`]: time::macros::format_description!
288    /// [`time::format_description::parse`]: time::format_description::parse
289    /// [`time` book]: https://time-rs.github.io/book/api/format-description.html
290    pub fn new(format: F) -> Self {
291        Self { format }
292    }
293}
294
295impl<F> FormatTime for UtcTime<F>
296where
297    F: Formattable,
298{
299    fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
300        format_datetime(OffsetDateTime::now_utc(), w, &self.format)
301    }
302}
303
304impl<F> Default for UtcTime<F>
305where
306    F: Formattable + Default,
307{
308    fn default() -> Self {
309        Self::new(F::default())
310    }
311}
312
313// === impl OffsetTime ===
314
315#[cfg(feature = "local-time")]
316impl OffsetTime<well_known::Rfc3339> {
317    /// Returns a formatter that formats the current time using the [local time offset] in the [RFC
318    /// 3339] format (a subset of the [ISO 8601] timestamp format).
319    ///
320    /// Returns an error if the local time offset cannot be determined. This typically occurs in
321    /// multithreaded programs. To avoid this problem, initialize `OffsetTime` before forking
322    /// threads. When using Tokio, this means initializing `OffsetTime` before the Tokio runtime.
323    ///
324    /// # Examples
325    ///
326    /// ```
327    /// use tracing_subscriber::fmt::{self, time};
328    ///
329    /// let subscriber = tracing_subscriber::fmt()
330    ///     .with_timer(time::OffsetTime::local_rfc_3339().expect("could not get local offset!"));
331    /// # drop(subscriber);
332    /// ```
333    ///
334    /// Using `OffsetTime` with Tokio:
335    ///
336    /// ```
337    /// use tracing_subscriber::fmt::time::OffsetTime;
338    ///
339    /// #[tokio::main]
340    /// async fn run() {
341    ///     tracing::info!("runtime initialized");
342    ///
343    ///     // At this point the Tokio runtime is initialized, and we can use both Tokio and Tracing
344    ///     // normally.
345    /// }
346    ///
347    /// fn main() {
348    ///     // Because we need to get the local offset before Tokio spawns any threads, our `main`
349    ///     // function cannot use `tokio::main`.
350    ///     tracing_subscriber::fmt()
351    ///         .with_timer(OffsetTime::local_rfc_3339().expect("could not get local time offset"))
352    ///         .init();
353    ///
354    ///     // Even though `run` is written as an `async fn`, because we used `tokio::main` on it
355    ///     // we can call it as a synchronous function.
356    ///     run();
357    /// }
358    /// ```
359    ///
360    /// [local time offset]: time::UtcOffset::current_local_offset
361    /// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
362    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
363    pub fn local_rfc_3339() -> Result<Self, time::error::IndeterminateOffset> {
364        Ok(Self::new(
365            UtcOffset::current_local_offset()?,
366            well_known::Rfc3339,
367        ))
368    }
369}
370
371impl<F: time::formatting::Formattable> OffsetTime<F> {
372    /// Returns a formatter that formats the current time using the [`time` crate] with the provided
373    /// provided format and [timezone offset]. The format may be any type that implements the
374    /// [`Formattable`] trait.
375    ///
376    ///
377    /// Typically, the offset will be the [local offset], and format will be a format description
378    /// string, or one of the `time` crate's [well-known formats].
379    ///
380    /// If the format description is statically known, then the
381    /// [`format_description!`] macro should be used. This is identical to the
382    /// [`time::format_description::parse`] method, but runs at compile-time,
383    /// throwing an error if the format description is invalid. If the desired format
384    /// is not known statically (e.g., a user is providing a format string), then the
385    /// [`time::format_description::parse`] method should be used. Note that this
386    /// method is fallible.
387    ///
388    /// See the [`time` book] for details on the format description syntax.
389    ///
390    /// # Examples
391    ///
392    /// Using the [`format_description!`] macro:
393    ///
394    /// ```
395    /// use tracing_subscriber::fmt::{self, time::OffsetTime};
396    /// use time::macros::format_description;
397    /// use time::UtcOffset;
398    ///
399    /// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
400    /// let timer = OffsetTime::new(offset, format_description!("[hour]:[minute]:[second]"));
401    /// let subscriber = tracing_subscriber::fmt()
402    ///     .with_timer(timer);
403    /// # drop(subscriber);
404    /// ```
405    ///
406    /// Using [`time::format_description::parse`]:
407    ///
408    /// ```
409    /// use tracing_subscriber::fmt::{self, time::OffsetTime};
410    /// use time::UtcOffset;
411    ///
412    /// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
413    /// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
414    ///     .expect("format string should be valid!");
415    /// let timer = OffsetTime::new(offset, time_format);
416    /// let subscriber = tracing_subscriber::fmt()
417    ///     .with_timer(timer);
418    /// # drop(subscriber);
419    /// ```
420    ///
421    /// Using the [`format_description!`] macro requires enabling the `time`
422    /// crate's "macros" feature flag.
423    ///
424    /// Using a [well-known format][well-known formats] (this is equivalent to
425    /// [`OffsetTime::local_rfc_3339`]):
426    ///
427    /// ```
428    /// use tracing_subscriber::fmt::{self, time::OffsetTime};
429    /// use time::UtcOffset;
430    ///
431    /// let offset = UtcOffset::current_local_offset().expect("should get local offset!");
432    /// let timer = OffsetTime::new(offset, time::format_description::well_known::Rfc3339);
433    /// let subscriber = tracing_subscriber::fmt()
434    ///     .with_timer(timer);
435    /// # drop(subscriber);
436    /// ```
437    ///
438    /// [`time` crate]: time
439    /// [timezone offset]: time::UtcOffset
440    /// [`Formattable`]: time::formatting::Formattable
441    /// [local offset]: time::UtcOffset::current_local_offset()
442    /// [well-known formats]: time::format_description::well_known
443    /// [`format_description!`]: time::macros::format_description
444    /// [`time::format_description::parse`]: time::format_description::parse
445    /// [`time` book]: https://time-rs.github.io/book/api/format-description.html
446    pub fn new(offset: time::UtcOffset, format: F) -> Self {
447        Self { offset, format }
448    }
449}
450
451impl<F> FormatTime for OffsetTime<F>
452where
453    F: time::formatting::Formattable,
454{
455    fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
456        let now = OffsetDateTime::now_utc().to_offset(self.offset);
457        format_datetime(now, w, &self.format)
458    }
459}
460
461fn format_datetime(
462    now: OffsetDateTime,
463    into: &mut Writer<'_>,
464    fmt: &impl Formattable,
465) -> fmt::Result {
466    let mut into = WriteAdaptor::new(into);
467    now.format_into(&mut into, fmt)
468        .map_err(|_| fmt::Error)
469        .map(|_| ())
470}