-
Notifications
You must be signed in to change notification settings - Fork 747
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
subscriber: add OffsetTime workaround for local offset #1772
Conversation
Currently, the `time` crate refuses to determine the local timezone offset on Unix unless the program is single-threaded or the `unsound_local_offset` feature is enabled. Because `LocalTime` determines the local timezone offset every time it formats a message, and `tracing` is frequently used in multi-threaded programs, `LocalTime` effectively requires `unsound_local_offset`. `unsound_local_offset` is inconvenient to enable and potentially dangerous. Add an `OffsetTime` formatter that formats time with a fixed offset, passed in when it is initialized. Make it convenient to initialize this with the local timezone offset. The main advantage of this formatter over `LocalTime` is that if it is initialized early, it will succeed without `unsound_local_offset`. An additional advantage is that the program can handle errors determining the local offset, because they now occur in the program's initialization code rather than in tracing's formatting code. This is not a drop-in replacement for LocalTime, because it behaves differently if the program keeps running across a DST change. `LocalTime` will pick up the new local offset, while `OffsetTime` will continue to use the offset in effect when the program started.
impl<F> FormatTime for OffsetTime<F> | ||
where | ||
F: time::formatting::Formattable, | ||
{ | ||
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result { | ||
let now = OffsetDateTime::now_utc().to_offset(self.offset); | ||
format_datetime(now, w, &self.format) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual formatting is not tested. My plan was to copy how LocalTime
is tested, but I couldn't find its tests :) Happy to improve this if I missed something, or if you give me some pointers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really need tests for how the timestamp is actually formatted, because the way it's formatted is "however the time
crate does it". :) Don't worry about adding tests for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, this seems like a good approach to me. I had a few small comments mostly related to documentation.
impl<F> FormatTime for OffsetTime<F> | ||
where | ||
F: time::formatting::Formattable, | ||
{ | ||
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result { | ||
let now = OffsetDateTime::now_utc().to_offset(self.offset); | ||
format_datetime(now, w, &self.format) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really need tests for how the timestamp is actually formatted, because the way it's formatted is "however the time
crate does it". :) Don't worry about adding tests for that.
/// | ||
/// Returns an error if the local time offset cannot be determined. This typically occurs in | ||
/// multithreaded programs. To avoid this problem, initialize `OffsetTime` before forking | ||
/// threads. When using Tokio, this means initializing `OffsetTime` before the Tokio runtime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be worth adding an example showing initializing the formatter before starting a runtime? not a blocker though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (I didn't realize I could use Tokio and write my own main
in examples like these...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can use Tokio because we have a dev dependency on tokio (it's used in a few other test).
Another approach I've used elsewhere in the documentation for tracing is to just do something like this:
//! # mod tokio {
//! # fn spawn(_: impl Future<> {}
//! # // fake versions of whatever other tokio APIs you need to show in the example...
//! # }
//! // the rest of the example...
/// [formatter]: https://docs.rs/time/0.3/time/formatting/trait.Formattable.html | ||
/// [`time` crate]: https://docs.rs/time/0.3/time/ | ||
#[derive(Clone, Debug)] | ||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this states that the type requires the time
feature, but it looks like all the methods implemented for this type also require the local-time
feature...should the type itself also require that feature flag?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OffsetTime::local_rfc_3339
requires local-time
, but OffsetTime::init
and OffsetTime::format_time
(our impl FormatTime
) do not. It should be possible (although admittedly unusual) to use OffsetTime
with a non-local offset (not needing time
's local-offset
feature at all). And if our caller uses OffsetTime::init
and calls current_local_offset
themselves it seemed reasonable to have them enable the local-offset
feature on the time
crate themselves, rather than having them use our local-time
feature just so we can turn on that time
feature for them (we won't be doing anything with local time ourselves).
Please let me know if I'm overlooking something.
ping @marienz, are you still planning on working on this? if not, i'm happy to apply the remaining suggestions so we can go ahead and get it merged! |
Comments (hopefully) addressed, sorry for the delay. |
Looks like the CI failure was a spurious GitHub Actions issue, so I've restarted it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this looks good to me. Thanks again for the PR!
/// | ||
/// Returns an error if the local time offset cannot be determined. This typically occurs in | ||
/// multithreaded programs. To avoid this problem, initialize `OffsetTime` before forking | ||
/// threads. When using Tokio, this means initializing `OffsetTime` before the Tokio runtime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can use Tokio because we have a dev dependency on tokio (it's used in a few other test).
Another approach I've used elsewhere in the documentation for tracing is to just do something like this:
//! # mod tokio {
//! # fn spawn(_: impl Future<> {}
//! # // fake versions of whatever other tokio APIs you need to show in the example...
//! # }
//! // the rest of the example...
# 0.3.5 (Dec 29, 2021) This release re-enables `RUST_LOG` filtering in `tracing_subscriber::fmt`'s default initialization methods, and adds an `OffsetLocalTime` formatter for using local timestamps with the `time` crate. ### Added - **fmt**: Added `OffsetLocalTime` formatter to `fmt::time` for formatting local timestamps with a fixed offset ([#1772]) ### Fixed - **fmt**: Added a `Targets` filter to `fmt::init()` and `fmt::try_init()` when the "env-filter" feature is disabled, so that `RUST_LOG` is still honored ([#1781]) Thanks to @marienz and @ishitatsuyuki for contributing to this release! [#1772]: #1772 [#1781]: #1781
# 0.3.5 (Dec 29, 2021) This release re-enables `RUST_LOG` filtering in `tracing_subscriber::fmt`'s default initialization methods, and adds an `OffsetLocalTime` formatter for using local timestamps with the `time` crate. ### Added - **fmt**: Added `OffsetLocalTime` formatter to `fmt::time` for formatting local timestamps with a fixed offset ([#1772]) ### Fixed - **fmt**: Added a `Targets` filter to `fmt::init()` and `fmt::try_init()` when the "env-filter" feature is disabled, so that `RUST_LOG` is still honored ([#1781]) Thanks to @marienz and @ishitatsuyuki for contributing to this release! [#1772]: #1772 [#1781]: #1781
hi, thank you for this nice support, but i have a question. I’m using tokio and setup the code by following your example, but the error came up. Any idea is appreciated.
The code is like below. I surely initialize tracing offset before async fn. // main.rs
use tracing_subscriber::fmt::time::OffsetTime;
use tracing_subscriber::EnvFilter;
fn main() {
dotenv::dotenv().ok();
let style = std::env::var("RUST_LOG_STYLE").unwrap_or_else(|_| "auto".into());
let tracing_format = std::env::var("TRACING_FORMAT").unwrap_or_else(|_| "ansi".into());
let builder = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_timer(OffsetTime::local_rfc_3339().expect("could not get local time offset"))
.with_ansi(style.to_lowercase() != "never");
match tracing_format.as_str() {
"json" => builder.json().init(),
_ => builder.init(),
};
if let Err(e) = run() {
tracing::error!("{:?}", e);
};
}
#[tokio::main]
async fn run() -> hyper::Result<()> {
//…
tracing::debug!("listening");
//…
} Cargo tool is like this. // Cargo.toml
tracing-subscriber = { version = "0.3.5", features = ["json", "env-filter", "local-time"] } My environment is like this
|
Unfortunately it looks like my example code will currently fail on MacOS (thanks for including the platform you're on!). For now, you'll need the special Although the
So once those crates release (or if you depend on a new enough git snapshot...), I'd expect your code to work. Until then, you might want to use (If you have any further issues, it's probably easier to file an issue and CC me on it than to use this pull request.) |
hi marienz, thank you for detail answer. yeah time seems not to support it until v3.5, while master is already fixed but released code is like this, so... thank you very much again, I'll wait then!!! |
0.3.6 time.rs is released a few hours ago, and now it's working. thank you very much time-rs/time@3ff4543#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4edR27 |
## Motivation Currently, the `time` crate refuses to determine the local timezone offset on Unix unless the program is single-threaded or the `unsound_local_offset` feature is enabled. Because `LocalTime` determines the local timezone offset every time it formats a message, and `tracing` is frequently used in multi-threaded programs, `LocalTime` effectively requires `unsound_local_offset`. `unsound_local_offset` is inconvenient to enable and potentially dangerous. ## Solution Add an `OffsetTime` formatter that formats time with a fixed offset, passed in when it is initialized. Make it convenient to initialize this with the local timezone offset. The main advantage of this formatter over `LocalTime` is that if it is initialized early, it will succeed without `unsound_local_offset`. An additional advantage is that the program can handle errors determining the local offset, because they now occur in the program's initialization code rather than in tracing's formatting code. This is not a drop-in replacement for LocalTime, because it behaves differently if the program keeps running across a DST change. `LocalTime` will pick up the new local offset, while `OffsetTime` will continue to use the offset in effect when the program started. Fixes tokio-rs#1771
# 0.3.5 (Dec 29, 2021) This release re-enables `RUST_LOG` filtering in `tracing_subscriber::fmt`'s default initialization methods, and adds an `OffsetLocalTime` formatter for using local timestamps with the `time` crate. ### Added - **fmt**: Added `OffsetLocalTime` formatter to `fmt::time` for formatting local timestamps with a fixed offset ([tokio-rs#1772]) ### Fixed - **fmt**: Added a `Targets` filter to `fmt::init()` and `fmt::try_init()` when the "env-filter" feature is disabled, so that `RUST_LOG` is still honored ([tokio-rs#1781]) Thanks to @marienz and @ishitatsuyuki for contributing to this release! [tokio-rs#1772]: tokio-rs#1772 [tokio-rs#1781]: tokio-rs#1781
Motivation
Currently, the
time
crate refuses to determine the local timezoneoffset on Unix unless the program is single-threaded or the
unsound_local_offset
feature is enabled. BecauseLocalTime
determines the local timezone offset every time it formats a message,
and
tracing
is frequently used in multi-threaded programs,LocalTime
effectively requires
unsound_local_offset
.unsound_local_offset
isinconvenient to enable and potentially dangerous.
Solution
Add an
OffsetTime
formatter that formats time with a fixed offset,passed in when it is initialized. Make it convenient to initialize this
with the local timezone offset.
The main advantage of this formatter over
LocalTime
is that if it isinitialized early, it will succeed without
unsound_local_offset
. Anadditional advantage is that the program can handle errors determining
the local offset, because they now occur in the program's initialization
code rather than in tracing's formatting code.
This is not a drop-in replacement for LocalTime, because it behaves
differently if the program keeps running across a DST change.
LocalTime
will pick up the new local offset, whileOffsetTime
willcontinue to use the offset in effect when the program started.
Fixes #1771