@@ -47,6 +47,7 @@ const DEFAULT_LOG_TARGETS: [Target; 2] = [
47
47
Target :: new ( TargetKind :: Stdout ) ,
48
48
Target :: new ( TargetKind :: LogDir { file_name : None } ) ,
49
49
] ;
50
+ const LOG_DATE_FORMAT : & str = "[year]-[month]-[day]_[hour]-[minute]-[second]" ;
50
51
51
52
#[ derive( Debug , thiserror:: Error ) ]
52
53
pub enum Error {
@@ -115,8 +116,12 @@ impl From<log::Level> for LogLevel {
115
116
}
116
117
117
118
pub enum RotationStrategy {
119
+ /// Will keep all the logs, renaming them to include the date.
118
120
KeepAll ,
121
+ /// Will only keep the most recent log up to its maximal size.
119
122
KeepOne ,
123
+ /// Will keep some of the most recent logs, renaming them to include the date.
124
+ KeepSome ( usize ) ,
120
125
}
121
126
122
127
#[ derive( Debug , Clone ) ]
@@ -577,6 +582,34 @@ pub fn attach_logger(
577
582
Ok ( ( ) )
578
583
}
579
584
585
+ fn rename_file_to_dated (
586
+ path : & impl AsRef < Path > ,
587
+ dir : & impl AsRef < Path > ,
588
+ file_name : & str ,
589
+ timezone_strategy : & TimezoneStrategy ,
590
+ ) -> Result < ( ) , Error > {
591
+ let to = dir. as_ref ( ) . join ( format ! (
592
+ "{}_{}.log" ,
593
+ file_name,
594
+ timezone_strategy
595
+ . get_now( )
596
+ . format( & time:: format_description:: parse( LOG_DATE_FORMAT ) . unwrap( ) )
597
+ . unwrap( ) ,
598
+ ) ) ;
599
+ if to. is_file ( ) {
600
+ // designated rotated log file name already exists
601
+ // highly unlikely but defensively handle anyway by adding .bak to filename
602
+ let mut to_bak = to. clone ( ) ;
603
+ to_bak. set_file_name ( format ! (
604
+ "{}.bak" ,
605
+ to_bak. file_name( ) . unwrap( ) . to_string_lossy( )
606
+ ) ) ;
607
+ fs:: rename ( & to, to_bak) ?;
608
+ }
609
+ fs:: rename ( path, to) ?;
610
+ Ok ( ( ) )
611
+ }
612
+
580
613
fn get_log_file_path (
581
614
dir : & impl AsRef < Path > ,
582
615
file_name : & str ,
@@ -591,34 +624,43 @@ fn get_log_file_path(
591
624
if log_size > max_file_size {
592
625
match rotation_strategy {
593
626
RotationStrategy :: KeepAll => {
594
- let to = dir. as_ref ( ) . join ( format ! (
595
- "{}_{}.log" ,
596
- file_name,
597
- timezone_strategy. get_now( ) . format( & format_description!(
598
- "[year]-[month]-[day]_[hour]-[minute]-[second]"
599
- ) ) ?,
600
- ) ) ;
601
- if to. is_file ( ) {
602
- // designated rotated log file name already exists
603
- // highly unlikely but defensively handle anyway by adding .bak to filename
604
- let mut to_bak = to. clone ( ) ;
605
- to_bak. set_file_name ( format ! (
606
- "{}.bak" ,
607
- to_bak
608
- . file_name( )
609
- . map( |f| f. to_string_lossy( ) )
610
- . unwrap_or_default( )
611
- ) ) ;
612
- fs:: rename ( & to, to_bak) ?;
627
+ rename_file_to_dated ( & path, dir, file_name, timezone_strategy) ?;
628
+ }
629
+ RotationStrategy :: KeepSome ( how_many) => {
630
+ let mut files = fs:: read_dir ( dir) ?
631
+ . filter_map ( |entry| {
632
+ let entry = entry. ok ( ) ?;
633
+ let path = entry. path ( ) ;
634
+ let old_file_name = path. file_name ( ) ?. to_string_lossy ( ) . into_owned ( ) ;
635
+ if old_file_name. starts_with ( file_name) {
636
+ let date = old_file_name
637
+ . strip_prefix ( file_name) ?
638
+ . strip_prefix ( "_" ) ?
639
+ . strip_suffix ( ".log" ) ?;
640
+ Some ( ( path, date. to_string ( ) ) )
641
+ } else {
642
+ None
643
+ }
644
+ } )
645
+ . collect :: < Vec < _ > > ( ) ;
646
+ // Regular sorting, so the oldest files are first. Lexicographical
647
+ // sorting is fine due to the date format.
648
+ files. sort_by ( |a, b| a. 1 . cmp ( & b. 1 ) ) ;
649
+ // We want to make space for the file we will be soon renaming, AND
650
+ // the file we will be creating. Thus we need to keep how_many - 2 files.
651
+ if files. len ( ) > ( * how_many - 2 ) {
652
+ files. truncate ( files. len ( ) + 2 - * how_many) ;
653
+ for ( old_log_path, _) in files {
654
+ fs:: remove_file ( old_log_path) ?;
655
+ }
613
656
}
614
- fs :: rename ( & path, to ) ?;
657
+ rename_file_to_dated ( & path, dir , file_name , timezone_strategy ) ?;
615
658
}
616
659
RotationStrategy :: KeepOne => {
617
660
fs:: remove_file ( & path) ?;
618
661
}
619
662
}
620
663
}
621
664
}
622
-
623
665
Ok ( path)
624
666
}
0 commit comments