Skip to content

Commit 7b08355

Browse files
committed
Respect local timezone when rotating files by time point
1 parent adf6c0d commit 7b08355

File tree

3 files changed

+143
-44
lines changed

3 files changed

+143
-44
lines changed

.github/workflows/ci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ jobs:
5555
steps:
5656
- name: Checkout repository
5757
uses: actions/checkout@v4
58+
- name: Set a non-UTC timezone # for detecting issues related to the local timezone
59+
uses: szenius/set-timezone@v2.0
60+
with:
61+
timezoneLinux: "Asia/Shanghai"
62+
timezoneMacos: "Asia/Shanghai"
63+
timezoneWindows: "China Standard Time"
5864
- name: Disable bench dependencies
5965
run: ./.github/workflows/disable-bench-deps.sh
6066
- name: Install dependencies

spdlog/src/sink/rotating_file_sink.rs

+136-43
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,13 @@ impl RotatorFileSizeInner {
450450

451451
impl RotatorTimePoint {
452452
fn new(
453+
override_now: Option<SystemTime>,
453454
base_path: PathBuf,
454455
time_point: TimePoint,
455456
max_files: usize,
456457
truncate: bool,
457458
) -> Result<Self> {
458-
let now = SystemTime::now();
459+
let now = override_now.unwrap_or_else(SystemTime::now);
459460
let file_path = Self::calc_file_path(base_path.as_path(), time_point, now);
460461
let file = utils::open_file(file_path, truncate)?;
461462

@@ -500,8 +501,8 @@ impl RotatorTimePoint {
500501
// constructor.
501502
#[must_use]
502503
fn next_rotation_time_point(time_point: TimePoint, now: SystemTime) -> SystemTime {
503-
let now: DateTime<Utc> = now.into();
504-
let mut rotation_time: DateTime<Utc> = now;
504+
let now: DateTime<Local> = now.into();
505+
let mut rotation_time = now;
505506

506507
match time_point {
507508
TimePoint::Daily { hour, minute } => {
@@ -768,6 +769,10 @@ impl RotatingFileSinkBuilder<PathBuf, RotationPolicy> {
768769
/// the file, [`Error::CreateDirectory`] or [`Error::OpenFile`] will be
769770
/// returned.
770771
pub fn build(self) -> Result<RotatingFileSink> {
772+
self.build_with_initial_time(None)
773+
}
774+
775+
fn build_with_initial_time(self, override_now: Option<SystemTime>) -> Result<RotatingFileSink> {
771776
self.rotation_policy
772777
.validate()
773778
.map_err(|err| Error::InvalidArgument(InvalidArgumentError::RotationPolicy(err)))?;
@@ -781,13 +786,15 @@ impl RotatingFileSinkBuilder<PathBuf, RotationPolicy> {
781786
)?),
782787
RotationPolicy::Daily { hour, minute } => {
783788
RotatorKind::TimePoint(RotatorTimePoint::new(
789+
override_now,
784790
self.base_path,
785791
TimePoint::Daily { hour, minute },
786792
self.max_files,
787793
self.rotate_on_open,
788794
)?)
789795
}
790796
RotationPolicy::Hourly => RotatorKind::TimePoint(RotatorTimePoint::new(
797+
override_now,
791798
self.base_path,
792799
TimePoint::Hourly,
793800
self.max_files,
@@ -811,7 +818,7 @@ mod tests {
811818

812819
static BASE_LOGS_PATH: Lazy<PathBuf> = Lazy::new(|| {
813820
let path = TEST_LOGS_PATH.join("rotating_file_sink");
814-
fs::create_dir(&path).unwrap();
821+
_ = fs::create_dir(&path);
815822
path
816823
});
817824

@@ -1036,10 +1043,32 @@ mod tests {
10361043

10371044
static LOGS_PATH: Lazy<PathBuf> = Lazy::new(|| {
10381045
let path = BASE_LOGS_PATH.join("policy_time_point");
1046+
_ = fs::remove_dir_all(&path);
10391047
fs::create_dir_all(&path).unwrap();
10401048
path
10411049
});
10421050

1051+
const SECOND_1: Duration = Duration::from_secs(1);
1052+
const HOUR_1: Duration = Duration::from_secs(60 * 60);
1053+
const DAY_1: Duration = Duration::from_secs(60 * 60 * 24);
1054+
1055+
#[track_caller]
1056+
fn assert_files_count(file_name_prefix: &str, expected: usize) {
1057+
let paths = fs::read_dir(LOGS_PATH.clone()).unwrap();
1058+
1059+
let mut filenames = Vec::new();
1060+
let actual = paths.fold(0_usize, |mut count, entry| {
1061+
let filename = entry.unwrap().file_name();
1062+
if filename.to_string_lossy().starts_with(file_name_prefix) {
1063+
count += 1;
1064+
filenames.push(filename);
1065+
}
1066+
count
1067+
});
1068+
println!("found files: {:?}", filenames);
1069+
assert_eq!(actual, expected)
1070+
}
1071+
10431072
#[test]
10441073
fn calc_file_path() {
10451074
let system_time = Local.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap().into();
@@ -1087,9 +1116,6 @@ mod tests {
10871116
#[test]
10881117
fn rotate() {
10891118
let build = |rotate_on_open| {
1090-
fs::remove_dir_all(LOGS_PATH.as_path()).unwrap();
1091-
fs::create_dir(LOGS_PATH.as_path()).unwrap();
1092-
10931119
let hourly_sink = RotatingFileSink::builder()
10941120
.base_path(LOGS_PATH.join("hourly.log"))
10951121
.rotation_policy(RotationPolicy::Hourly)
@@ -1114,61 +1140,128 @@ mod tests {
11141140
logger
11151141
};
11161142

1117-
let exist_files = |file_name_prefix| {
1118-
let paths = fs::read_dir(LOGS_PATH.clone()).unwrap();
1119-
1120-
paths.fold(0_usize, |count, entry| {
1121-
if entry
1122-
.unwrap()
1123-
.file_name()
1124-
.to_string_lossy()
1125-
.starts_with(file_name_prefix)
1126-
{
1127-
count + 1
1128-
} else {
1129-
count
1130-
}
1131-
})
1132-
};
1133-
1134-
let exist_hourly_files = || exist_files("hourly");
1135-
let exist_daily_files = || exist_files("daily");
1136-
1137-
const SECOND_1: Duration = Duration::from_secs(1);
1138-
const HOUR_1: Duration = Duration::from_secs(60 * 60);
1139-
const DAY_1: Duration = Duration::from_secs(60 * 60 * 24);
1140-
11411143
{
11421144
let logger = build(true);
11431145
let mut record = Record::new(Level::Info, "test log message");
11441146
let initial_time = record.time();
11451147

1146-
assert_eq!(exist_hourly_files(), 1);
1147-
assert_eq!(exist_daily_files(), 1);
1148+
assert_files_count("hourly", 1);
1149+
assert_files_count("daily", 1);
11481150

11491151
logger.log(&record);
1150-
assert_eq!(exist_hourly_files(), 1);
1151-
assert_eq!(exist_daily_files(), 1);
1152+
assert_files_count("hourly", 1);
1153+
assert_files_count("daily", 1);
11521154

11531155
record.set_time(record.time() + HOUR_1 + SECOND_1);
11541156
logger.log(&record);
1155-
assert_eq!(exist_hourly_files(), 2);
1156-
assert_eq!(exist_daily_files(), 1);
1157+
assert_files_count("hourly", 2);
1158+
assert_files_count("daily", 1);
11571159

11581160
record.set_time(record.time() + HOUR_1 + SECOND_1);
11591161
logger.log(&record);
1160-
assert_eq!(exist_hourly_files(), 3);
1161-
assert_eq!(exist_daily_files(), 1);
1162+
assert_files_count("hourly", 3);
1163+
assert_files_count("daily", 1);
11621164

11631165
record.set_time(record.time() + SECOND_1);
11641166
logger.log(&record);
1165-
assert_eq!(exist_hourly_files(), 3);
1166-
assert_eq!(exist_daily_files(), 1);
1167+
assert_files_count("hourly", 3);
1168+
assert_files_count("daily", 1);
11671169

11681170
record.set_time(initial_time + DAY_1 + SECOND_1);
11691171
logger.log(&record);
1170-
assert_eq!(exist_hourly_files(), 4);
1171-
assert_eq!(exist_daily_files(), 2);
1172+
assert_files_count("hourly", 4);
1173+
assert_files_count("daily", 2);
1174+
}
1175+
}
1176+
1177+
// This test may only detect issues if the system time zone is not UTC.
1178+
#[test]
1179+
fn respect_local_tz() {
1180+
let prefix = "respect_local_tz";
1181+
1182+
let initial_time = Local // FixedOffset::east_opt(8 * 3600).unwrap()
1183+
.with_ymd_and_hms(2024, 8, 29, 11, 45, 14)
1184+
.unwrap();
1185+
1186+
let logger = {
1187+
let daily_sink = RotatingFileSink::builder()
1188+
.base_path(LOGS_PATH.join(format!("{prefix}.log")))
1189+
.rotation_policy(RotationPolicy::Daily { hour: 0, minute: 0 })
1190+
.rotate_on_open(true)
1191+
.build_with_initial_time(Some(initial_time.to_utc().into()))
1192+
.unwrap();
1193+
1194+
build_test_logger(|b| b.sink(Arc::new(daily_sink)).level_filter(LevelFilter::All))
1195+
};
1196+
1197+
{
1198+
let mut record = Record::new(Level::Info, "test log message");
1199+
1200+
assert_files_count(prefix, 1);
1201+
1202+
record.set_time(initial_time.to_utc().into());
1203+
logger.log(&record);
1204+
assert_files_count(prefix, 1);
1205+
1206+
record.set_time(record.time() + HOUR_1 + SECOND_1);
1207+
logger.log(&record);
1208+
assert_files_count(prefix, 1);
1209+
1210+
record.set_time(record.time() + HOUR_1 + SECOND_1);
1211+
logger.log(&record);
1212+
assert_files_count(prefix, 1);
1213+
1214+
record.set_time(
1215+
initial_time
1216+
.with_day(30)
1217+
.unwrap()
1218+
.with_hour(0)
1219+
.unwrap()
1220+
.with_minute(1)
1221+
.unwrap()
1222+
.to_utc()
1223+
.into(),
1224+
);
1225+
logger.log(&record);
1226+
assert_files_count(prefix, 2);
1227+
1228+
record.set_time(record.time() + HOUR_1 + SECOND_1);
1229+
logger.log(&record);
1230+
assert_files_count(prefix, 2);
1231+
1232+
record.set_time(
1233+
initial_time
1234+
.with_day(30)
1235+
.unwrap()
1236+
.with_hour(8)
1237+
.unwrap()
1238+
.with_minute(2)
1239+
.unwrap()
1240+
.to_utc()
1241+
.into(),
1242+
);
1243+
logger.log(&record);
1244+
assert_files_count(prefix, 2);
1245+
1246+
record.set_time(record.time() + HOUR_1 + SECOND_1);
1247+
logger.log(&record);
1248+
assert_files_count(prefix, 2);
1249+
1250+
record.set_time(
1251+
initial_time
1252+
.with_day(31)
1253+
.unwrap()
1254+
.with_hour(0)
1255+
.unwrap()
1256+
.to_utc()
1257+
.into(),
1258+
);
1259+
logger.log(&record);
1260+
assert_files_count(prefix, 3);
1261+
1262+
record.set_time(record.time() + HOUR_1 + SECOND_1);
1263+
logger.log(&record);
1264+
assert_files_count(prefix, 3);
11721265
}
11731266
}
11741267
}

spdlog/src/test_utils/unit_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ use crate::sync::*;
1212

1313
pub static TEST_LOGS_PATH: Lazy<PathBuf> = Lazy::new(|| {
1414
let path = Path::new(env!("OUT_DIR")).join("test_logs");
15-
fs::create_dir(&path).unwrap();
15+
_ = fs::create_dir(&path);
1616
path
1717
});

0 commit comments

Comments
 (0)