1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
#[cfg(not(feature = "use_chrono_for_offset"))]
use crate::util::{eprint_err, ERRCODE};
#[cfg(feature = "use_chrono_for_offset")]
use chrono::{Local, Offset};
use std::sync::{Arc, Mutex};
#[cfg(feature = "syslog_writer")]
use time::Month;
use time::{formatting::Formattable, OffsetDateTime, UtcOffset};
/// Deferred timestamp creation.
///
/// Is used to ensure that a log record that is sent to multiple outputs
/// (in maybe different formats) always uses the same timestamp.
#[derive(Debug, Default)]
pub struct DeferredNow(Option<OffsetDateTime>);
impl DeferredNow {
/// Constructs a new instance, but does not generate the timestamp.
#[must_use]
pub fn new() -> Self {
Self(None)
}
/// Retrieve the timestamp.
///
/// Requires mutability because the first caller will generate the timestamp.
pub fn now(&mut self) -> &OffsetDateTime {
self.0.get_or_insert_with(Self::now_local)
}
/// Convert into a formatted String.
///
/// # Panics
///
/// Panics if `fmt` has an inappropriate value.
pub fn format(&mut self, fmt: &(impl Formattable + ?Sized)) -> String {
self.now().format(fmt).unwrap(/* ok */)
}
#[cfg(feature = "syslog_writer")]
pub(crate) fn format_rfc3339(&mut self) -> String {
self.format(&time::format_description::well_known::Rfc3339)
}
// format_rfc3164: Mmm dd hh:mm:ss, where
// mmm = one of "Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec",
// dd = "xy" where x = " " or "1" or "2" or "3"
// hh = "00" ... "23"
// mm, ss= "00" ... "59"
#[cfg(feature = "syslog_writer")]
pub(crate) fn format_rfc3164(&mut self) -> String {
let now = self.now();
format!(
"{mmm} {dd:>2} {hh:02}:{mm:02}:{ss:02}",
mmm = match now.month() {
Month::January => "Jan",
Month::February => "Feb",
Month::March => "Mar",
Month::April => "Apr",
Month::May => "May",
Month::June => "Jun",
Month::July => "Jul",
Month::August => "Aug",
Month::September => "Sep",
Month::October => "Oct",
Month::November => "Nov",
Month::December => "Dec",
},
dd = now.day(),
hh = now.hour(),
mm = now.minute(),
ss = now.second()
)
}
/// Enforce the use of UTC rather than local time.
///
/// By default, `flexi_logger` uses or tries to use local time.
/// By calling early in your program either `Logger::use_utc()` or directly this method,
/// you can override this to always use UTC.
///
/// # Panics
///
/// Panics if called too late, i.e., if [`DeferredNow::now`] was already called before on
/// any instance of `DeferredNow`.
pub fn force_utc() {
let mut guard = FORCE_UTC.lock().unwrap();
match *guard {
Some(false) => {
panic!("offset is already initialized not to enforce UTC");
}
Some(true) => {
// is already set, nothing to do
}
None => *guard = Some(true),
}
}
// Get the current timestamp, usually in local time.
//
// This method retrieves the timezone offset only once and caches it then.
// This is to mitigate the issue of the `time` crate
// (see their [CHANGELOG](https://github.com/time-rs/time/blob/main/CHANGELOG.md#035-2021-11-12))
// that determining the offset is not safely working on linux,
// and is not even tried there if the program is multi-threaded, or on other Unix-like systems.
//
// The method is called a first time during the initialization of `flexi_logger`,
// and when the initialization is done while the program is single-threaded,
// this should produce the right time offset in the trace output on linux.
// On Windows and Mac there are no such limitations.
//
// If `Logger::use_utc()` is used, then this method will always return a UTC timestamp.
#[doc(hidden)]
#[must_use]
pub fn now_local() -> OffsetDateTime {
OffsetDateTime::now_utc().to_offset(*OFFSET)
}
}
// Due to https://rustsec.org/advisories/RUSTSEC-2020-0159
// we obtain the offset only once and keep it here
lazy_static::lazy_static! {
static ref OFFSET: UtcOffset = {
let mut force_utc_guard = FORCE_UTC.lock().unwrap();
if let Some(true) = *force_utc_guard { UtcOffset::UTC } else {
if force_utc_guard.is_none() {
*force_utc_guard = Some(false);
}
#[cfg(feature = "use_chrono_for_offset")]
{
let chrono_offset_seconds = Local::now().offset().fix().local_minus_utc();
UtcOffset::from_whole_seconds(chrono_offset_seconds).unwrap(/* ok */)
}
#[cfg(not(feature = "use_chrono_for_offset"))]
{
match OffsetDateTime::now_local() {
Ok(ts) => {ts.offset()},
Err(e) => {
eprint_err(
ERRCODE::Time,
"flexi_logger works with UTC rather than with local time",
&e,
);
UtcOffset::UTC
}
}
}
}
};
}
// now_local() takes the offset from the lazy_static OFFSET, and this should be cheap.
// At the same time we want to influence the value in OFFSET based on whether Logger::use_utc()
// is used.
// Logger::use_utc() thus modifies the (expensive) lazy_static FORCE_UTC, and then the (cheap)
// lazy_static OFFSET is filled in the first invocation of now_local().
lazy_static::lazy_static! {
static ref FORCE_UTC: Arc<Mutex<Option<bool>>> =
Arc::new(Mutex::new(None));
}
#[cfg(test)]
mod test {
#[test]
fn test_deferred_now() {
let mut deferred_now = super::DeferredNow::new();
let once = deferred_now.now().to_string();
println!("This should be the current timestamp: {}", once);
std::thread::sleep(std::time::Duration::from_millis(300));
let again = deferred_now.now().to_string();
println!("This must be the same timestamp: {}", again);
assert_eq!(once, again);
}
#[cfg(feature = "syslog_writer")]
#[test]
fn test_format_rfc3164() {
// println!(
// "{mmm} {dd:>2} {hh:02}:{mm:02}:{ss:02}",
// mmm = "Jan",
// dd = 1,
// hh = 2,
// mm = 3,
// ss = 4
// );
let mut deferred_now = super::DeferredNow::new();
println!("rfc3164: {}", deferred_now.format_rfc3164());
}
}