Skip to content

Conversation

@snakefoot
Copy link
Contributor

Resolves #6055

@snakefoot snakefoot added this to the 6.0.7 milestone Dec 6, 2025
@snakefoot snakefoot added enhancement Improvement on existing feature file-target file-archiving Issues with archiving with the file target labels Dec 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Walkthrough

Replaces simple string comparison with a new natural string comparer in BaseFileArchiveHandler to order archive filenames when creation dates tie, and updates FileTarget unit tests to accept and pass a new maxArchiveFiles parameter to GenerateArchives.

Changes

Cohort / File(s) Summary
Natural comparison for archive file sorting
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
Adds private static int NaturalStringComparer(string x, string y) implementing a natural sort (compare numeric blocks by length first, then digit-by-digit; non-digit chars compared case-insensitively; length fallback). Uses this comparator in Compare when FileCreatedTimeUtc.Date are equal instead of StringComparer.OrdinalIgnoreCase.
Archive file test parameterization and helper updates
tests/NLog.UnitTests/Targets/FileTargetTests.cs
Changes test signature to (int sequenceNumber, string expectedSequenceInArchiveFileName, int maxArchiveFiles), updates InlineData and archive template to use {##}, and extends GenerateArchives(int count, string archiveDateFormat, string archiveFileName, int maxArchiveFiles = 0) to set fileTarget.MaxArchiveFiles when maxArchiveFiles > 0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review areas:
    • NaturalStringComparer logic: numeric-block detection, length-first tiebreak, per-digit comparison, non-digit case-insensitive handling, and fallback behavior.
    • Integration in BaseFileArchiveHandler.Compare to ensure it's applied only when creation dates tie and preserves stable ordering.
    • Test adjustments in FileTargetTests.cs: GenerateArchives signature and correct application of MaxArchiveFiles.

Poem

🐰 I hopped through filenames, counting digits by sight,

I sorted the numbers by length, then by each little byte.
No more muddled hops at the edge of the line,
Sequences march onward in tidy design.
A carrot-cheer for tidy archive time!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing natural order sorting for archive filenames in FileTarget archive cleanup.
Description check ✅ Passed The description references issue #6055, which is related to the changeset about archive file sorting and sequence number handling.
Linked Issues check ✅ Passed The PR implements natural string comparison for archive filenames, which addresses issue #6055 by properly sorting filenames with numeric components when sequence numbers exceed formatted digit width.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing natural order sorting for archive filenames: a new NaturalStringComparer method and updated test parameterization to validate the sorting behavior.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdc4ddb and 6abc135.

📒 Files selected for processing (2)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1 hunks)
  • tests/NLog.UnitTests/Targets/FileTargetTests.cs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (1)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

146-160: Natural filename sort logic looks sound for archive cleanup use case

The switch to NaturalStringComparer when dates tie (Line 154) correctly addresses numeric ordering of names like ..._99 vs ..._100, avoiding lexicographic mis-ordering that could cause high sequence numbers to be deleted first. The implementation:

  • Compares shared numeric blocks by length (after skipping leading zeros) and then digit-by-digit.
  • Falls back to case-insensitive ordinal comparison for non-digit segments.
  • Uses total length as a final tie-breaker, which is fine for file names.

I don’t see correctness issues for archive file patterns used here, and the behavior on edge cases (leading zeros, differing digit widths) is reasonable. No changes needed from my side.

Also applies to: 162-235

Comment on lines 3438 to 3454
[Theory]
[InlineData("##", 0, "00")]
[InlineData("##", 1, "01")]
[InlineData("#", 20, "20")]
[InlineData(0, "00", 0)]
[InlineData(1, "01", 0)]
[InlineData(20, "20", 0)]
[InlineData(101, "101", 10)]
public void FileTarget_WithDateAndSequenceArchiveNumbering_ShouldPadSequenceNumberInArchiveFileName(
string placeHolderSharps, int sequenceNumber, string expectedSequenceInArchiveFileName)
int sequenceNumber, string expectedSequenceInArchiveFileName, int maxArchiveFiles)
{
string tempDir = Path.Combine(Path.GetTempPath(), "nlog_" + Guid.NewGuid().ToString());
const string archiveDateFormat = "yyyy-MM-dd";
string archiveFileName = Path.Combine(tempDir, $"{{{placeHolderSharps}}}.log");
string archiveFileName = Path.Combine(tempDir, $"{{##}}.log");
string expectedArchiveFullName =
$"{tempDir}/{DateTime.Now.ToString(archiveDateFormat)}_{expectedSequenceInArchiveFileName}.log";

GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat,
archiveFileName: archiveFileName);
archiveFileName: archiveFileName, maxArchiveFiles);
bool resultArchiveWithExpectedNameExists = File.Exists(expectedArchiveFullName);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix GenerateArchives call: positional arg after named args will not compile

The new GenerateArchives signature and wiring of maxArchiveFiles look good, but this call mixes named and positional arguments:

GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat,
    archiveFileName: archiveFileName, maxArchiveFiles);

In C#, once you start using named arguments, all subsequent arguments must also be named; this will be a compile error on the supported targets.

Recommend making the last argument named as well (or reverting to all-positional):

-            GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat,
-                archiveFileName: archiveFileName, maxArchiveFiles);
+            GenerateArchives(
+                count: sequenceNumber + 2,
+                archiveDateFormat: archiveDateFormat,
+                archiveFileName: archiveFileName,
+                maxArchiveFiles: maxArchiveFiles);

The rest of the changes (extra test parameter and the optional maxArchiveFiles in GenerateArchives) nicely exercise the interaction between sequence numbering and archive cleanup.

Also applies to: 3477-3493

🤖 Prompt for AI Agents
In tests/NLog.UnitTests/Targets/FileTargetTests.cs around lines 3438 to 3454
(and similarly at 3477-3493), the GenerateArchives call mixes named and
positional arguments which will not compile; make the final argument named as
well (e.g., maxArchiveFiles: maxArchiveFiles) or convert all arguments to
positional so all arguments follow the same style; update both occurrences
accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

162-238: Natural string comparer logic looks sound for numeric archive suffixes; consider small clarity/test follow-ups

The implementation correctly handles the core natural-sort cases for archive filenames:

  • Numeric blocks are compared by significant length after trimming leading zeros, then digit-by-digit, which yields the expected order for ..._9, ..._10, ..._99, ..._100, etc.
  • Non-digit segments are compared case-insensitively and deterministically.
  • When there are no impactful numeric differences (or only fully equal numeric blocks), the final length comparison gives a stable tie-breaker.

Two optional polish ideas:

  • Add a brief comment explaining why equal-length strings are delegated directly to string.Compare (fixed-width numeric segments + constant suffixes means ordinal is already numeric-correct).
  • Back this helper with a focused unit-test set that exercises edge cases (e.g., mixed leading zeros, multiple numeric blocks, 01 vs 1, end-of-string numeric blocks) so future refactors don’t accidentally regress the ordering.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6abc135 and c413964.

📒 Files selected for processing (2)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1 hunks)
  • tests/NLog.UnitTests/Targets/FileTargetTests.cs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/NLog.UnitTests/Targets/FileTargetTests.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (1)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

146-159: Switching to NaturalStringComparer for same-day files aligns with the archive cleanup goal

Using NaturalStringComparer only when ArchiveSequenceNumber is unavailable and the dates match keeps the existing primary sort key (sequence number) intact while fixing the lexicographic-vs-numeric ordering problem for filenames like ..._99 vs ..._100. The change is localized, deterministic, and consistent with the delete-from-oldest behavior in CleanupFiles.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
tests/NLog.UnitTests/Targets/FileTargetTests.cs (1)

3452-3453: Fix compilation error: positional argument after named arguments.

This issue was previously identified in past review comments. The call mixes named and positional arguments, which is a compilation error in C#. Once you use named arguments, all subsequent arguments must also be named.

Apply this diff:

-            GenerateArchives(count: sequenceNumber + 2, archiveDateFormat: archiveDateFormat,
-                archiveFileName: archiveFileName, maxArchiveFiles);
+            GenerateArchives(
+                count: sequenceNumber + 2,
+                archiveDateFormat: archiveDateFormat,
+                archiveFileName: archiveFileName,
+                maxArchiveFiles: maxArchiveFiles);
🧹 Nitpick comments (1)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

162-164: Clarify version reference in XML comment.

The comment mentions "NET10" which is ambiguous. Consider clarifying as ".NET 10.0" or ".NET 10" for better readability.

Apply this diff:

 /// <summary>
-/// With NET10 there is CompareOptions.NumericOrdering (But not supported on all operating systems)
+/// With .NET 10.0 there is CompareOptions.NumericOrdering (But not supported on all operating systems)
 /// </summary>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c413964 and 2058050.

📒 Files selected for processing (2)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1 hunks)
  • tests/NLog.UnitTests/Targets/FileTargetTests.cs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/NLog.UnitTests/Targets/FileTargetTests.cs (1)
src/NLog/Targets/FileTarget.cs (2)
  • DateTime (837-855)
  • DateTime (865-873)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (4)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (2)

152-155: LGTM! Natural sort integration is correctly scoped.

The replacement of StringComparer.OrdinalIgnoreCase with NaturalStringComparer is applied only when creation dates are equal, preserving the existing sort hierarchy (sequence number → date → filename). This ensures that archive files like file_99.txt and file_100.txt will be ordered correctly even when the sequence number exceeds the formatted digit width.


165-238: Approve the natural string comparer implementation.

The implementation correctly handles natural sorting for archive filenames:

  • Numeric block comparison: Compares by length first (longer numbers are greater), then digit-by-digit for same-length numbers, which correctly orders sequences like "99" < "100"
  • Leading zero handling: Properly skips leading zeros so "001" and "1" are treated as equal numerically
  • Prefix optimization: Skips the common prefix before comparing, improving efficiency
  • Fallback logic: Uses length comparison as the final tiebreaker

This addresses issue #6055 where archive sequence numbers exceeding the formatted digit width (e.g., 99 → 100) were not sorted correctly.

tests/NLog.UnitTests/Targets/FileTargetTests.cs (2)

3438-3444: Test parameterization updated appropriately.

The test signature changes support testing the natural sort fix for archive cleanup:

  • Replaced placeHolderSharps with direct use of {##} pattern (line 3448)
  • Added maxArchiveFiles parameter to verify archive cleanup behavior with both unlimited (0) and limited (10) scenarios
  • Test cases now validate the scenario from issue #6055 where sequence numbers exceed formatted width (line 3442: sequenceNumber=101)

3477-3493: LGTM! GenerateArchives helper method updated correctly.

The addition of the optional maxArchiveFiles parameter with appropriate conditional logic is well-designed:

  • Default value of 0 allows existing test calls to work unchanged (e.g., line 3471)
  • Only sets MaxArchiveFiles when > 0, which correctly handles the unlimited-archives case
  • Enables testing of archive cleanup behavior with the new natural sort implementation

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 6, 2025

@snakefoot snakefoot merged commit c7abc83 into NLog:dev Dec 6, 2025
5 of 6 checks passed
@snakefoot snakefoot changed the title FileTarget - Archive Cleanup sorting filenames using natural order FileTarget - Archive Cleanup sorting filenames using natural ordering Dec 6, 2025
@snakefoot snakefoot changed the title FileTarget - Archive Cleanup sorting filenames using natural ordering FileTarget - Archive Cleanup sort filenames using natural ordering Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement on existing feature file-archiving Issues with archiving with the file target file-target size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Archive files are lost when the sequence number in archive suffix reaches the highest possible number

1 participant