-
Notifications
You must be signed in to change notification settings - Fork 799
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
Logging improvements #1500
Logging improvements #1500
Changes from all commits
def2fc9
bb29ab5
f075aa1
7b73100
93b78fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,19 +17,28 @@ | |
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
use chrono::{Datelike, Timelike}; | ||
use std::{cell::RefCell, fmt::Write, time::SystemTime}; | ||
use std::{cell::RefCell, fmt, fmt::Write, time::SystemTime}; | ||
use tracing_subscriber::fmt::time::FormatTime; | ||
|
||
/// A structure which, when `Display`d, will print out the current local time. | ||
/// A structure which, when `Display`d, will print out the current time either in local timezone or | ||
/// in UTC. | ||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] | ||
pub struct FastLocalTime { | ||
/// Decides whenever to use UTC timezone instead. | ||
/// | ||
/// If `true` the output will match the following `chrono` format string: | ||
/// `%Y-%m-%dT%H:%M:%S%.6fZ` | ||
/// | ||
/// This is the same format used by [`tracing_subscriber`] by default, `with_fractional` has no | ||
/// effect when this option is set to `true`. | ||
pub utc: bool, | ||
/// Decides whenever the fractional timestamp with be included in the output. | ||
/// | ||
/// If `false` the output will match the following `chrono` format string: | ||
/// `%Y-%m-%d %H:%M:%S` | ||
/// `%Y-%m-%d %H:%M:%S%:z` | ||
/// | ||
/// If `true` the output will match the following `chrono` format string: | ||
/// `%Y-%m-%d %H:%M:%S%.3f` | ||
/// `%Y-%m-%d %H:%M:%S%.3f%:z` | ||
pub with_fractional: bool, | ||
} | ||
|
||
|
@@ -43,7 +52,7 @@ struct InlineString { | |
} | ||
|
||
impl Write for InlineString { | ||
fn write_str(&mut self, s: &str) -> std::fmt::Result { | ||
fn write_str(&mut self, s: &str) -> fmt::Result { | ||
let new_length = self.length + s.len(); | ||
assert!( | ||
new_length <= TIMESTAMP_MAXIMUM_LENGTH, | ||
|
@@ -66,8 +75,10 @@ impl InlineString { | |
|
||
#[derive(Default)] | ||
struct CachedTimestamp { | ||
timezone_offset: InlineString, | ||
buffer: InlineString, | ||
last_regenerated_at: u64, | ||
last_utc: bool, | ||
last_fractional: u32, | ||
} | ||
|
||
|
@@ -76,7 +87,8 @@ thread_local! { | |
} | ||
|
||
impl FormatTime for FastLocalTime { | ||
fn format_time(&self, w: &mut dyn Write) -> std::fmt::Result { | ||
fn format_time(&self, w: &mut dyn Write) -> fmt::Result { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you rewrite this so that:
This way there will only be a single toplevel |
||
const TIMESTAMP_PARTIAL_UTC_LENGTH: usize = "0000-00-00T00:00:00".len(); | ||
const TIMESTAMP_PARTIAL_LENGTH: usize = "0000-00-00 00:00:00".len(); | ||
|
||
let elapsed = SystemTime::now() | ||
|
@@ -88,24 +100,59 @@ impl FormatTime for FastLocalTime { | |
let mut cache = cache.borrow_mut(); | ||
|
||
// Regenerate the timestamp only at most once each second. | ||
if cache.last_regenerated_at != unix_time { | ||
let ts = chrono::Local::now(); | ||
let fractional = (ts.nanosecond() % 1_000_000_000) / 1_000_000; | ||
cache.last_regenerated_at = unix_time; | ||
cache.last_fractional = fractional; | ||
cache.buffer.length = 0; | ||
|
||
write!( | ||
&mut cache.buffer, | ||
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}", | ||
ts.year(), | ||
ts.month(), | ||
ts.day(), | ||
ts.hour(), | ||
ts.minute(), | ||
ts.second(), | ||
fractional | ||
)?; | ||
if cache.last_regenerated_at != unix_time || cache.last_utc != self.utc { | ||
cache.last_utc = self.utc; | ||
|
||
if self.utc { | ||
let ts = chrono::Utc::now(); | ||
let fractional = (ts.nanosecond() % 1_000_000_000) / 1_000; | ||
cache.last_regenerated_at = unix_time; | ||
cache.last_fractional = fractional; | ||
cache.buffer.length = 0; | ||
|
||
write!( | ||
&mut cache.buffer, | ||
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z", | ||
ts.year(), | ||
ts.month(), | ||
ts.day(), | ||
ts.hour(), | ||
ts.minute(), | ||
ts.second(), | ||
fractional | ||
)?; | ||
} else { | ||
let ts = chrono::Local::now(); | ||
let fractional = (ts.nanosecond() % 1_000_000_000) / 1_000_000; | ||
cache.last_regenerated_at = unix_time; | ||
cache.last_fractional = fractional; | ||
cache.buffer.length = 0; | ||
|
||
write!( | ||
&mut cache.buffer, | ||
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}", | ||
ts.year(), | ||
ts.month(), | ||
ts.day(), | ||
ts.hour(), | ||
ts.minute(), | ||
ts.second(), | ||
fractional | ||
)?; | ||
|
||
if cache.timezone_offset.length == 0 { | ||
write!(&mut cache.timezone_offset, "{}", ts.offset())?; | ||
} | ||
Comment on lines
+143
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory the current timezone can change, so this should not assume that the timezone stays constant. This is only done once per second, so its fine to do this unconditionally. |
||
} | ||
} else if self.utc { | ||
let fractional = elapsed.subsec_micros(); | ||
|
||
// Regenerate the fractional part at most once each millisecond. | ||
if cache.last_fractional != fractional { | ||
cache.last_fractional = fractional; | ||
cache.buffer.length = TIMESTAMP_PARTIAL_UTC_LENGTH + 1; | ||
write!(&mut cache.buffer, "{:06}Z", fractional)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6 digits for the fractional part seems somewhat excessive; why not use 3 like in the non-UTC case? The logger's asynchronous anyway so this much precision is practically useless. It'll just take extra space in the logs for no real benefit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason is to make it identical to what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, that's reasonable if that's the |
||
} | ||
} else if self.with_fractional { | ||
let fractional = elapsed.subsec_millis(); | ||
|
||
|
@@ -118,37 +165,53 @@ impl FormatTime for FastLocalTime { | |
} | ||
|
||
let mut slice = cache.buffer.as_str(); | ||
if !self.with_fractional { | ||
slice = &slice[..TIMESTAMP_PARTIAL_LENGTH]; | ||
|
||
if self.utc { | ||
w.write_str(slice)?; | ||
} else { | ||
Comment on lines
+169
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Either we make it obey |
||
if !self.with_fractional { | ||
slice = &slice[..TIMESTAMP_PARTIAL_LENGTH]; | ||
} | ||
|
||
w.write_str(slice)?; | ||
w.write_str(cache.timezone_offset.as_str())?; | ||
} | ||
|
||
w.write_str(slice) | ||
Ok(()) | ||
}) | ||
} | ||
} | ||
|
||
impl std::fmt::Display for FastLocalTime { | ||
fn fmt(&self, w: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
impl fmt::Display for FastLocalTime { | ||
fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { | ||
self.format_time(w) | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_format_fast_local_time() { | ||
assert_eq!( | ||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string().len(), | ||
FastLocalTime { with_fractional: false }.to_string().len() | ||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S%:z").to_string().len(), | ||
FastLocalTime { utc: false, with_fractional: false }.to_string().len() | ||
); | ||
assert_eq!( | ||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string().len(), | ||
FastLocalTime { utc: false, with_fractional: true }.to_string().len() | ||
); | ||
assert_eq!( | ||
chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string().len(), | ||
FastLocalTime { utc: true, with_fractional: false }.to_string().len() | ||
); | ||
assert_eq!( | ||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string().len(), | ||
FastLocalTime { with_fractional: true }.to_string().len() | ||
chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string().len(), | ||
FastLocalTime { utc: true, with_fractional: true }.to_string().len() | ||
); | ||
|
||
// A simple trick to make sure this test won't randomly fail if we so happen | ||
// to land on the exact moment when we tick over to the next second. | ||
let now_1 = FastLocalTime { with_fractional: false }.to_string(); | ||
let expected = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); | ||
let now_2 = FastLocalTime { with_fractional: false }.to_string(); | ||
let now_1 = FastLocalTime { utc: false, with_fractional: false }.to_string(); | ||
let expected = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%:z").to_string(); | ||
let now_2 = FastLocalTime { utc: false, with_fractional: false }.to_string(); | ||
|
||
assert!( | ||
now_1 == expected || now_2 == expected, | ||
|
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.
Hm, I'd probably prefer to have this take a parameter, e.g.
--timestamp-format=$format
where $format can belocal
(default) oriso8601
(since what you've done is not just to force UTC timestamps - you've changed the format of the timestamp to be like iso8601). It's also more forward compatible if we'd want to add other formats, or make the timestamp format fully configurable with anstrftime
-like string.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 like that, but ISO 8601 is not just UTC, so I it seems like there should still be a distinction between UTC and non-UTC in logs if you really want to have local time there (I suspect that is current expectation from Substrate users that would be undesirable to change).
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.
Well then, it could be
iso8601-utc
I guess?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.
Hm, I read about ISO 8601, what a mess... There are so many way to compose valid ISO 8601 date and time that it is hardly helpful to name an option like that. Technically both local and UTC time in PR description are valid RFC 3339 formats, so the only difference between them is local vs UTC time and
between date and time (both valid).
T
vsWhat about something like this?:
--timestamp-format=default
--timestamp-format=tracing-subscriber
default
is the current default of Substrate,tracing-subscriber
is format used in that crate. Standard doesn't say anything about number of digits for fractional seconds, so both 3 and 6 is valid according to RFC 3339.Here is helpful visualization: https://ijmacd.github.io/rfc3339-iso8601/