Skip to content

Commit c7abc83

Browse files
authored
FileTarget - Archive Cleanup sorting filenames using natural order (#6056)
1 parent aa5566c commit c7abc83

File tree

2 files changed

+91
-8
lines changed

2 files changed

+91
-8
lines changed

src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,92 @@ public int Compare(FileInfoDateTime x, FileInfoDateTime y)
151151
}
152152
else if (x.FileCreatedTimeUtc.Date == y.FileCreatedTimeUtc.Date)
153153
{
154-
return StringComparer.OrdinalIgnoreCase.Compare(x.FileInfo.Name, y.FileInfo.Name);
154+
return NaturalStringComparer(x.FileInfo.Name, y.FileInfo.Name);
155155
}
156156
else
157157
{
158158
return x.FileCreatedTimeUtc.CompareTo(y.FileCreatedTimeUtc);
159159
}
160160
}
161161

162+
/// <summary>
163+
/// With NET10 there is CompareOptions.NumericOrdering (But not supported on all operating systems)
164+
/// </summary>
165+
private static int NaturalStringComparer(string x, string y)
166+
{
167+
if (x is null || y is null || x.Length == y.Length)
168+
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
169+
170+
int x_pos = 0;
171+
int y_pos = 0;
172+
173+
// Skip common prefix
174+
int digit_start = -1;
175+
while (x_pos < x.Length && y_pos < y.Length)
176+
{
177+
if (x[x_pos] != y[y_pos])
178+
break;
179+
180+
if (!char.IsDigit(x[x_pos]))
181+
digit_start = -1;
182+
else if (digit_start < 0)
183+
digit_start = x_pos;
184+
y_pos++;
185+
x_pos++;
186+
}
187+
if (digit_start >= 0)
188+
{
189+
x_pos = digit_start;
190+
y_pos = digit_start;
191+
}
192+
193+
while (x_pos < x.Length && y_pos < y.Length)
194+
{
195+
char x_chr = x[x_pos];
196+
char y_chr = y[y_pos];
197+
198+
// Both chars are digits, compare numeric block
199+
if (char.IsDigit(x_chr) && char.IsDigit(y_chr))
200+
{
201+
// Skip leading zeros
202+
while (x_pos < x.Length && x[x_pos] == '0') x_pos++;
203+
while (y_pos < y.Length && y[y_pos] == '0') y_pos++;
204+
205+
int x_digit_start = x_pos;
206+
int y_digit_start = y_pos;
207+
while (x_pos < x.Length && char.IsDigit(x[x_pos])) x_pos++;
208+
while (y_pos < y.Length && char.IsDigit(y[y_pos])) y_pos++;
209+
210+
int x_digit_len = x_pos - x_digit_start;
211+
int y_digit_len = y_pos - y_digit_start;
212+
213+
// If different length, longer wins
214+
if (x_digit_len != y_digit_len)
215+
return x_digit_len.CompareTo(y_digit_len);
216+
217+
// Lengths equal, compare digit by digit
218+
for (int i = 0; i < x_digit_len; i++)
219+
{
220+
int cmp = x[x_digit_start + i].CompareTo(y[y_digit_start + i]);
221+
if (cmp != 0)
222+
return cmp;
223+
}
224+
// If all digits equal, continue comparing after number block
225+
continue;
226+
}
227+
228+
// Non-digit comparison (case-insensitive, ordinal)
229+
int cmpChar = char.ToUpperInvariant(x_chr).CompareTo(char.ToUpperInvariant(y_chr));
230+
if (cmpChar != 0)
231+
return cmpChar;
232+
233+
x_pos++;
234+
y_pos++;
235+
}
236+
237+
return x.Length.CompareTo(y.Length);
238+
}
239+
162240
public override string ToString()
163241
{
164242
return FileInfo.Name;

tests/NLog.UnitTests/Targets/FileTargetTests.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3436,20 +3436,21 @@ public static string GenerateTempPath()
34363436
}
34373437

34383438
[Theory]
3439-
[InlineData("##", 0, "00")]
3440-
[InlineData("##", 1, "01")]
3441-
[InlineData("#", 20, "20")]
3439+
[InlineData(0, "00", 0)]
3440+
[InlineData(1, "01", 0)]
3441+
[InlineData(20, "20", 0)]
3442+
[InlineData(101, "101", 10)]
34423443
public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldPadSequenceNumberInArchiveFileName(
3443-
string placeHolderSharps, int sequenceNumber, string expectedSequenceInArchiveFileName)
3444+
int sequenceNumber, string expectedSequenceInArchiveFileName, int maxArchiveFiles)
34443445
{
34453446
string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString());
34463447
const string archiveDateFormat = "yyyy-MM-dd";
3447-
string archiveFileName = Path.Combine(tempDir, $"{{{placeHolderSharps}}}.log");
3448+
string archiveFileName = Path.Combine(tempDir, $"{{##}}.log");
34483449
string expectedArchiveFullName =
34493450
$"{tempDir}/{DateTime.Now.ToString(archiveDateFormat)}_{expectedSequenceInArchiveFileName}.log";
34503451

34513452
GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat,
3452-
archiveFileName: archiveFileName);
3453+
archiveFileName: archiveFileName, maxArchiveFiles);
34533454
bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName);
34543455
Assert.True(resultArchiveWithExpectedNameExists);
34553456
}
@@ -3473,7 +3474,7 @@ public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldRespectArchiveD
34733474
Assert.True(resultArchiveWithExpectedNameExists);
34743475
}
34753476

3476-
private void GenerateArchives(int count, string archiveDateFormat, string archiveFileName)
3477+
private void GenerateArchives(int count, string archiveDateFormat, string archiveFileName, int maxArchiveFiles = 0)
34773478
{
34783479
string logFileName = Path.GetTempFileName();
34793480
const int logFileMaxSize = 1;
@@ -3486,6 +3487,10 @@ private void GenerateArchives(int count, string archiveDateFormat, string archiv
34863487
#pragma warning restore CS0618 // Type or member is obsolete
34873488
ArchiveAboveSize = logFileMaxSize
34883489
};
3490+
if (maxArchiveFiles > 0)
3491+
{
3492+
fileTarget.MaxArchiveFiles = maxArchiveFiles;
3493+
}
34893494
LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(fileTarget));
34903495
for (int currentSequenceNumber = 0; currentSequenceNumber < count; currentSequenceNumber++)
34913496
logger.Debug("Test {0}", currentSequenceNumber);

0 commit comments

Comments
 (0)