1212using Microsoft . AspNetCore . Testing ;
1313using Microsoft . Extensions . Hosting . Internal ;
1414using Microsoft . Extensions . Logging . Abstractions ;
15+ using Microsoft . Extensions . Logging . Testing ;
1516using Microsoft . Net . Http . Headers ;
1617using Xunit ;
1718
@@ -23,6 +24,8 @@ public class FileLoggerProcessorTests
2324 private string _messageOne = "Message one" ;
2425 private string _messageTwo = "Message two" ;
2526 private string _messageThree = "Message three" ;
27+ private string _messageFour = "Message four" ;
28+ private readonly DateTime _today = DateTime . UtcNow ;
2629
2730 public FileLoggerProcessorTests ( )
2831 {
@@ -190,6 +193,60 @@ public async Task RespectsMaxFileCount()
190193 }
191194 }
192195
196+ [ Fact ]
197+ public async Task StopsLoggingAfter10000Files ( )
198+ {
199+ var path = Path . Combine ( TempPath , Path . GetRandomFileName ( ) ) ;
200+ Directory . CreateDirectory ( path ) ;
201+
202+ try
203+ {
204+ string lastFileName ;
205+ var options = new W3CLoggerOptions ( )
206+ {
207+ LogDirectory = path ,
208+ FileSizeLimit = 5 ,
209+ RetainedFileCountLimit = 10000
210+ } ;
211+ var testSink = new TestSink ( ) ;
212+ var testLogger = new TestLoggerFactory ( testSink , enabled : true ) ;
213+ await using ( var logger = new FileLoggerProcessor ( new OptionsWrapperMonitor < W3CLoggerOptions > ( options ) , new HostingEnvironment ( ) , testLogger ) )
214+ {
215+ for ( int i = 0 ; i < 10000 ; i ++ )
216+ {
217+ logger . EnqueueMessage ( _messageOne ) ;
218+ }
219+ lastFileName = Path . Combine ( path , FormattableString . Invariant ( $ "{ options . FileName } { _today . Year : 0000} { _today . Month : 00} { _today . Day : 00} .9999.txt") ) ;
220+ await WaitForFile ( lastFileName , _messageOne . Length ) . DefaultTimeout ( ) ;
221+
222+ // directory is full, no warnings yet
223+ Assert . Equal ( 0 , testSink . Writes . Count ) ;
224+
225+ logger . EnqueueMessage ( _messageOne ) ;
226+ await WaitForCondition ( ( ) => testSink . Writes . FirstOrDefault ( ) ? . EventId . Name == "MaxFilesReached" ) . DefaultTimeout ( ) ;
227+ }
228+
229+ Assert . Equal ( 10000 , new DirectoryInfo ( path )
230+ . GetFiles ( )
231+ . ToArray ( ) . Length ) ;
232+
233+ // restarting the logger should do nothing since the folder is still full
234+ var testSink2 = new TestSink ( ) ;
235+ var testLogger2 = new TestLoggerFactory ( testSink2 , enabled : true ) ;
236+ await using ( var logger = new FileLoggerProcessor ( new OptionsWrapperMonitor < W3CLoggerOptions > ( options ) , new HostingEnvironment ( ) , testLogger2 ) )
237+ {
238+ Assert . Equal ( 0 , testSink2 . Writes . Count ) ;
239+
240+ logger . EnqueueMessage ( _messageOne ) ;
241+ await WaitForCondition ( ( ) => testSink2 . Writes . FirstOrDefault ( ) ? . EventId . Name == "MaxFilesReached" ) . DefaultTimeout ( ) ;
242+ }
243+ }
244+ finally
245+ {
246+ Helpers . DisposeDirectory ( path ) ;
247+ }
248+ }
249+
193250 [ Fact ]
194251 public async Task InstancesWriteToSameDirectory ( )
195252 {
@@ -340,6 +397,66 @@ public async Task WritesToNewFileOnNewInstance()
340397 }
341398 }
342399
400+ [ Fact ]
401+ public async Task RollsTextFilesWhenFirstLogOfDayIsMissing ( )
402+ {
403+ var path = Path . Combine ( TempPath , Path . GetRandomFileName ( ) ) ;
404+ Directory . CreateDirectory ( path ) ;
405+
406+ try
407+ {
408+ var options = new W3CLoggerOptions ( )
409+ {
410+ LogDirectory = path ,
411+ FileSizeLimit = 5 ,
412+ RetainedFileCountLimit = 2 ,
413+ } ;
414+ var fileName1 = Path . Combine ( path , FormattableString . Invariant ( $ "{ options . FileName } { _today . Year : 0000} { _today . Month : 00} { _today . Day : 00} .0000.txt") ) ;
415+ var fileName2 = Path . Combine ( path , FormattableString . Invariant ( $ "{ options . FileName } { _today . Year : 0000} { _today . Month : 00} { _today . Day : 00} .0001.txt") ) ;
416+ var fileName3 = Path . Combine ( path , FormattableString . Invariant ( $ "{ options . FileName } { _today . Year : 0000} { _today . Month : 00} { _today . Day : 00} .0002.txt") ) ;
417+ var fileName4 = Path . Combine ( path , FormattableString . Invariant ( $ "{ options . FileName } { _today . Year : 0000} { _today . Month : 00} { _today . Day : 00} .0003.txt") ) ;
418+
419+ await using ( var logger = new FileLoggerProcessor ( new OptionsWrapperMonitor < W3CLoggerOptions > ( options ) , new HostingEnvironment ( ) , NullLoggerFactory . Instance ) )
420+ {
421+ logger . EnqueueMessage ( _messageOne ) ;
422+ logger . EnqueueMessage ( _messageTwo ) ;
423+ logger . EnqueueMessage ( _messageThree ) ;
424+ // Pause for a bit before disposing so logger can finish logging
425+ await WaitForFile ( fileName3 , _messageThree . Length ) . DefaultTimeout ( ) ;
426+ }
427+
428+ // Even with a big enough FileSizeLimit, we still won't try to write to files from a previous instance.
429+ options . FileSizeLimit = 10000 ;
430+
431+ await using ( var logger = new FileLoggerProcessor ( new OptionsWrapperMonitor < W3CLoggerOptions > ( options ) , new HostingEnvironment ( ) , NullLoggerFactory . Instance ) )
432+ {
433+ logger . EnqueueMessage ( _messageFour ) ;
434+ // Pause for a bit before disposing so logger can finish logging
435+ await WaitForFile ( fileName4 , _messageFour . Length ) . DefaultTimeout ( ) ;
436+ }
437+
438+ var actualFiles = new DirectoryInfo ( path )
439+ . GetFiles ( )
440+ . Select ( f => f . Name )
441+ . OrderBy ( f => f )
442+ . ToArray ( ) ;
443+
444+ Assert . Equal ( 2 , actualFiles . Length ) ;
445+
446+ Assert . False ( File . Exists ( fileName1 ) ) ;
447+ Assert . False ( File . Exists ( fileName2 ) ) ;
448+ Assert . True ( File . Exists ( fileName3 ) ) ;
449+ Assert . True ( File . Exists ( fileName4 ) ) ;
450+
451+ Assert . Equal ( _messageThree + Environment . NewLine , File . ReadAllText ( fileName3 ) ) ;
452+ Assert . Equal ( _messageFour + Environment . NewLine , File . ReadAllText ( fileName4 ) ) ;
453+ }
454+ finally
455+ {
456+ Helpers . DisposeDirectory ( path ) ;
457+ }
458+ }
459+
343460 [ QuarantinedTest ( "https://github.com/dotnet/aspnetcore/issues/34982" ) ]
344461 [ Fact ]
345462 public async Task WritesToNewFileOnOptionsChange ( )
@@ -420,6 +537,14 @@ private async Task WaitForFile(string fileName, int length)
420537 }
421538 }
422539
540+ private async Task WaitForCondition ( Func < bool > waitForLog )
541+ {
542+ while ( ! waitForLog ( ) )
543+ {
544+ await Task . Delay ( 10 ) ;
545+ }
546+ }
547+
423548 private async Task WaitForRoll ( string fileName )
424549 {
425550 while ( File . Exists ( fileName ) )
0 commit comments