Skip to content

Commit a29515e

Browse files
Determine next log file number from directory on first run (#41492)
1 parent 4dae9d4 commit a29515e

File tree

2 files changed

+157
-10
lines changed

2 files changed

+157
-10
lines changed

src/Middleware/HttpLogging/src/FileLoggerProcessor.cs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,28 @@ private async Task WriteMessagesAsync(List<string> messages, CancellationToken c
156156
{
157157
// Files are written up to _maxFileSize before rolling to a new file
158158
DateTime today = DateTime.Now;
159+
160+
if (!TryCreateDirectory())
161+
{
162+
// return early if we fail to create the directory
163+
return;
164+
}
165+
159166
var fullName = GetFullName(today);
160167
// Don't write to an incomplete file left around by a previous FileLoggerProcessor
161168
if (_firstFile)
162169
{
163-
while (File.Exists(fullName))
170+
_fileNumber = GetFirstFileCount(today);
171+
fullName = GetFullName(today);
172+
if (_fileNumber >= W3CLoggerOptions.MaxFileCount)
164173
{
165-
_fileNumber++;
166-
if (_fileNumber >= W3CLoggerOptions.MaxFileCount)
167-
{
168-
_maxFilesReached = true;
169-
// Return early if log directory is already full
170-
Log.MaxFilesReached(_logger);
171-
return;
172-
}
173-
fullName = GetFullName(today);
174+
_maxFilesReached = true;
175+
// Return early if log directory is already full
176+
Log.MaxFilesReached(_logger);
177+
return;
174178
}
175179
}
180+
176181
_firstFile = false;
177182
if (_maxFilesReached)
178183
{
@@ -300,6 +305,23 @@ public async ValueTask DisposeAsync()
300305
await _outputTask;
301306
}
302307

308+
private int GetFirstFileCount(DateTime date)
309+
{
310+
lock (_pathLock)
311+
{
312+
var searchString = FormattableString.Invariant($"{_fileName}{date.Year:0000}{date.Month:00}{date.Day:00}.*.txt");
313+
var files = new DirectoryInfo(_path)
314+
.GetFiles(searchString);
315+
316+
return files.Length == 0
317+
? 0
318+
: files
319+
.Max(x => int.TryParse(x.Name.Split('.').ElementAtOrDefault(Index.FromEnd(2)), out var parsed)
320+
? parsed + 1
321+
: 0);
322+
}
323+
}
324+
303325
private string GetFullName(DateTime date)
304326
{
305327
lock (_pathLock)

src/Middleware/HttpLogging/test/FileLoggerProcessorTests.cs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.AspNetCore.Testing;
1313
using Microsoft.Extensions.Hosting.Internal;
1414
using Microsoft.Extensions.Logging.Abstractions;
15+
using Microsoft.Extensions.Logging.Testing;
1516
using Microsoft.Net.Http.Headers;
1617
using 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

Comments
 (0)