@@ -450,12 +450,13 @@ impl RotatorFileSizeInner {
450
450
451
451
impl RotatorTimePoint {
452
452
fn new (
453
+ override_now : Option < SystemTime > ,
453
454
base_path : PathBuf ,
454
455
time_point : TimePoint ,
455
456
max_files : usize ,
456
457
truncate : bool ,
457
458
) -> Result < Self > {
458
- let now = SystemTime :: now ( ) ;
459
+ let now = override_now . unwrap_or_else ( SystemTime :: now) ;
459
460
let file_path = Self :: calc_file_path ( base_path. as_path ( ) , time_point, now) ;
460
461
let file = utils:: open_file ( file_path, truncate) ?;
461
462
@@ -500,8 +501,8 @@ impl RotatorTimePoint {
500
501
// constructor.
501
502
#[ must_use]
502
503
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;
505
506
506
507
match time_point {
507
508
TimePoint :: Daily { hour, minute } => {
@@ -768,6 +769,10 @@ impl RotatingFileSinkBuilder<PathBuf, RotationPolicy> {
768
769
/// the file, [`Error::CreateDirectory`] or [`Error::OpenFile`] will be
769
770
/// returned.
770
771
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 > {
771
776
self . rotation_policy
772
777
. validate ( )
773
778
. map_err ( |err| Error :: InvalidArgument ( InvalidArgumentError :: RotationPolicy ( err) ) ) ?;
@@ -781,13 +786,15 @@ impl RotatingFileSinkBuilder<PathBuf, RotationPolicy> {
781
786
) ?) ,
782
787
RotationPolicy :: Daily { hour, minute } => {
783
788
RotatorKind :: TimePoint ( RotatorTimePoint :: new (
789
+ override_now,
784
790
self . base_path ,
785
791
TimePoint :: Daily { hour, minute } ,
786
792
self . max_files ,
787
793
self . rotate_on_open ,
788
794
) ?)
789
795
}
790
796
RotationPolicy :: Hourly => RotatorKind :: TimePoint ( RotatorTimePoint :: new (
797
+ override_now,
791
798
self . base_path ,
792
799
TimePoint :: Hourly ,
793
800
self . max_files ,
@@ -811,7 +818,7 @@ mod tests {
811
818
812
819
static BASE_LOGS_PATH : Lazy < PathBuf > = Lazy :: new ( || {
813
820
let path = TEST_LOGS_PATH . join ( "rotating_file_sink" ) ;
814
- fs:: create_dir ( & path) . unwrap ( ) ;
821
+ _ = fs:: create_dir ( & path) ;
815
822
path
816
823
} ) ;
817
824
@@ -1036,10 +1043,32 @@ mod tests {
1036
1043
1037
1044
static LOGS_PATH : Lazy < PathBuf > = Lazy :: new ( || {
1038
1045
let path = BASE_LOGS_PATH . join ( "policy_time_point" ) ;
1046
+ _ = fs:: remove_dir_all ( & path) ;
1039
1047
fs:: create_dir_all ( & path) . unwrap ( ) ;
1040
1048
path
1041
1049
} ) ;
1042
1050
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
+
1043
1072
#[ test]
1044
1073
fn calc_file_path ( ) {
1045
1074
let system_time = Local . with_ymd_and_hms ( 2012 , 3 , 4 , 5 , 6 , 7 ) . unwrap ( ) . into ( ) ;
@@ -1087,9 +1116,6 @@ mod tests {
1087
1116
#[ test]
1088
1117
fn rotate ( ) {
1089
1118
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
-
1093
1119
let hourly_sink = RotatingFileSink :: builder ( )
1094
1120
. base_path ( LOGS_PATH . join ( "hourly.log" ) )
1095
1121
. rotation_policy ( RotationPolicy :: Hourly )
@@ -1114,61 +1140,128 @@ mod tests {
1114
1140
logger
1115
1141
} ;
1116
1142
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
-
1141
1143
{
1142
1144
let logger = build ( true ) ;
1143
1145
let mut record = Record :: new ( Level :: Info , "test log message" ) ;
1144
1146
let initial_time = record. time ( ) ;
1145
1147
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 ) ;
1148
1150
1149
1151
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 ) ;
1152
1154
1153
1155
record. set_time ( record. time ( ) + HOUR_1 + SECOND_1 ) ;
1154
1156
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 ) ;
1157
1159
1158
1160
record. set_time ( record. time ( ) + HOUR_1 + SECOND_1 ) ;
1159
1161
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 ) ;
1162
1164
1163
1165
record. set_time ( record. time ( ) + SECOND_1 ) ;
1164
1166
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 ) ;
1167
1169
1168
1170
record. set_time ( initial_time + DAY_1 + SECOND_1 ) ;
1169
1171
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 ) ;
1172
1265
}
1173
1266
}
1174
1267
}
0 commit comments