@@ -48,6 +48,15 @@ class AdvancedFileOutput extends LogOutput {
48
48
///
49
49
/// [path] is either treated as directory for rotating or as target file name,
50
50
/// depending on [maxFileSizeKB] .
51
+ ///
52
+ /// [maxRotatedFilesCount] controls the number of rotated files to keep. By default
53
+ /// is null, which means no limit.
54
+ /// If set to a positive number, the output will keep the last
55
+ /// [maxRotatedFilesCount] files. The deletion step will be executed by sorting
56
+ /// files following the [fileSorter] ascending strategy and keeping the last files.
57
+ /// The [latestFileName] will not be counted. The default [fileSorter] strategy is
58
+ /// sorting by last modified date, beware that could be not reliable in some
59
+ /// platforms and/or filesystems.
51
60
AdvancedFileOutput ({
52
61
required String path,
53
62
bool overrideExisting = false ,
@@ -58,6 +67,8 @@ class AdvancedFileOutput extends LogOutput {
58
67
int maxFileSizeKB = 1024 ,
59
68
String latestFileName = 'latest.log' ,
60
69
String Function (DateTime timestamp)? fileNameFormatter,
70
+ int ? maxRotatedFilesCount,
71
+ Comparator <File >? fileSorter,
61
72
}) : _path = path,
62
73
_overrideExisting = overrideExisting,
63
74
_encoding = encoding,
@@ -73,6 +84,8 @@ class AdvancedFileOutput extends LogOutput {
73
84
// ignore: deprecated_member_use_from_same_package
74
85
Level .wtf,
75
86
],
87
+ _maxRotatedFilesCount = maxRotatedFilesCount,
88
+ _fileSorter = fileSorter ?? _defaultFileSorter,
76
89
_file = maxFileSizeKB > 0 ? File ('$path /$latestFileName ' ) : File (path);
77
90
78
91
/// Logs directory path by default, particular log file path if [_maxFileSizeKB] is 0.
@@ -86,6 +99,8 @@ class AdvancedFileOutput extends LogOutput {
86
99
final int _maxFileSizeKB;
87
100
final int _maxBufferSize;
88
101
final String Function (DateTime timestamp) _fileNameFormatter;
102
+ final int ? _maxRotatedFilesCount;
103
+ final Comparator <File > _fileSorter;
89
104
90
105
final File _file;
91
106
IOSink ? _sink;
@@ -106,6 +121,14 @@ class AdvancedFileOutput extends LogOutput {
106
121
'-${t .millisecond .toDigits (3 )}.log' ;
107
122
}
108
123
124
+ /// Sort files by their last modified date.
125
+ /// This behaviour is inspired by the Log4j PathSorter.
126
+ ///
127
+ /// This method fulfills the requirements of the [Comparator] interface.
128
+ static int _defaultFileSorter (File a, File b) {
129
+ return a.lastModifiedSync ().compareTo (b.lastModifiedSync ());
130
+ }
131
+
109
132
@override
110
133
Future <void > init () async {
111
134
if (_rotatingFilesMode) {
@@ -157,6 +180,7 @@ class AdvancedFileOutput extends LogOutput {
157
180
// Rotate the log file
158
181
await _closeSink ();
159
182
await _file.rename ('$_path /${_fileNameFormatter (DateTime .now ())}' );
183
+ await _deleteRotatedFiles ();
160
184
await _openSink ();
161
185
}
162
186
} catch (e, s) {
@@ -181,6 +205,35 @@ class AdvancedFileOutput extends LogOutput {
181
205
_sink = null ; // Explicitly set null until assigned again
182
206
}
183
207
208
+ Future <void > _deleteRotatedFiles () async {
209
+ // If maxRotatedFilesCount is not set, keep all files
210
+ if (_maxRotatedFilesCount == null ) return ;
211
+
212
+ final dir = Directory (_path);
213
+ final files = dir
214
+ .listSync ()
215
+ .whereType <File >()
216
+ // Filter out the latest file
217
+ .where ((f) => f.path != _file.path)
218
+ .toList ();
219
+
220
+ // If the number of files is less than the limit, don't delete anything
221
+ if (files.length <= _maxRotatedFilesCount! ) return ;
222
+
223
+ files.sort (_fileSorter);
224
+
225
+ final filesToDelete =
226
+ files.sublist (0 , files.length - _maxRotatedFilesCount! );
227
+ for (final file in filesToDelete) {
228
+ try {
229
+ await file.delete ();
230
+ } catch (e, s) {
231
+ print ('Failed to delete file: $e ' );
232
+ print (s);
233
+ }
234
+ }
235
+ }
236
+
184
237
@override
185
238
Future <void > destroy () async {
186
239
_bufferFlushTimer? .cancel ();
0 commit comments