-
Notifications
You must be signed in to change notification settings - Fork 847
subscriber: add OffsetTime workaround for local offset #1772
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
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.
hawkw
left a comment
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. |
hawkw
left a comment
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
timecrate refuses to determine the local timezoneoffset on Unix unless the program is single-threaded or the
unsound_local_offsetfeature is enabled. BecauseLocalTimedetermines the local timezone offset every time it formats a message,
and
tracingis frequently used in multi-threaded programs,LocalTimeeffectively requires
unsound_local_offset.unsound_local_offsetisinconvenient to enable and potentially dangerous.
Solution
Add an
OffsetTimeformatter 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
LocalTimeis 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.
LocalTimewill pick up the new local offset, whileOffsetTimewillcontinue to use the offset in effect when the program started.
Fixes #1771