Skip to content

Conversation

@snakefoot
Copy link
Contributor

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

Walkthrough

FileInfoHelper.LookupValidFileCreationTimeUtc changed to return non-null DateTime. Callers in appenders were updated to use the non-null return and year-based branching. Archive handler tie-break now uses FileCreatedTimeUtc.Date; CleanupFiles uses the helper’s raw DateTime without fallback or hour-truncation.

Changes

Cohort / File(s) Summary
File creation helper (API & internals)
src/NLog/Internal/FileInfoHelper.cs
Changed LookupValidFileCreationTimeUtc to return non-null DateTime. Removed nullable handling and HasValue checks; internal helper signatures updated to accept/return non-null delegates/values; introduced MinReliableBirthYear and only use finalFallback when year < 1980.
File appenders (use of creation time)
src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs, src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs
Removed null-coalescing fallbacks; directly use non-null DateTime from helper. Switched birth-time branching to Year-based checks against MinReliableBirthYear/OpenStreamTime; added explicit FileLastModified assignment in Exclusive appender.
Archive handler (sorting & cleanup)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
Tie-break in FileInfoDateTime.Compare now compares FileCreatedTimeUtc.Date (day-level). CleanupFiles uses helper’s raw DateTime directly (removed fallback to current time and truncation to hour), preserving full precision for per-file processing and sequence-number logic.
Unit test (AutoFlush)
tests/NLog.UnitTests/Targets/FileTargetTests.cs
Replaced fixed Thread.Sleep with a polling loop that waits for file existence and non-zero length (50ms intervals, up to 100 tries) plus 500ms final sleep before asserting file contents.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Appender as Appender
  participant Helper as FileInfoHelper
  participant FS as FileInfo

  Appender->>Helper: LookupValidFileCreationTimeUtc(fileInfo)
  Helper->>FS: Read CreationTimeUtc (with fallbacks if Year < MinReliableBirthYear)
  FS-->>Helper: DateTime (non-null UTC)
  Helper-->>Appender: DateTime
  Note right of Appender #DDEEFF: Year-based branching\n(e.g., Year > MinReliableBirthYear → FromSystemTime, else OpenStreamTime)
  Appender->>Appender: Decide FromSystemTime vs OpenStreamTime\nUpdate FileBirthTime / FileLastModified
Loading
sequenceDiagram
  autonumber
  participant Archiver as BaseFileArchiveHandler
  participant Helper as FileInfoHelper
  participant FS as FileInfo

  Archiver->>Helper: LookupValidFileCreationTimeUtc(fileInfo)
  Helper->>FS: Read CreationTimeUtc
  FS-->>Helper: DateTime (UTC)
  Helper-->>Archiver: DateTime
  Note right of Archiver #EFEFEF: CleanupFiles uses raw DateTime\nTie-break comparison uses FileCreatedTimeUtc.Date (day-level)
  Archiver->>Archiver: Compute sorting keys\nSort and cleanup files (date + name/seq)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I nibbled timestamps in the glen,
No ghostly nulls to worry then.
Dates now land with steady thump,
Appenders hop and archives jump.
A tidy burrow, logs tucked in—good night. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

Pre-merge checks (2 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive There is no pull request description provided, so it is impossible to determine whether it sufficiently describes the changes or relates to the modifications in the code. Please add a description summarizing the scope and intent of the changes in this pull request to ensure it accurately reflects the modifications made.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The current title clearly identifies the primary change by referencing the FileInfoHelper.LookupValidFileCreationTimeUtc method and the removal of its nullable DateTime return type, providing a concise and accurate summary of the main API modification.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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.

@snakefoot snakefoot added the file-archiving Issues with archiving with the file target label Sep 10, 2025
@snakefoot snakefoot enabled auto-merge (squash) September 10, 2025 05:50
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

138-151: Align sorting to “day-only” in local time to match deletion policy

To preserve the PR’s “date-only” ordering but avoid UTC/local drift, compare by the local Date for both sides.

             public int Compare(FileInfoDateTime x, FileInfoDateTime y)
             {
                 if (x.ArchiveSequenceNumber.HasValue && y.ArchiveSequenceNumber.HasValue)
                 {
                     return x.ArchiveSequenceNumber.Value.CompareTo(y.ArchiveSequenceNumber.Value);
                 }
-                else if (x.FileCreatedTimeUtc == y.FileCreatedTimeUtc)
+                var xDate = NLog.Time.TimeSource.Current.FromSystemTime(x.FileCreatedTimeUtc).Date;
+                var yDate = NLog.Time.TimeSource.Current.FromSystemTime(y.FileCreatedTimeUtc).Date;
+                if (xDate == yDate)
                 {
                     return StringComparer.OrdinalIgnoreCase.Compare(x.FileInfo.Name, y.FileInfo.Name);
                 }
-                else
-                {
-                    return x.FileCreatedTimeUtc.CompareTo(y.FileCreatedTimeUtc);
-                }
+                return xDate.CompareTo(yDate);
             }
🧹 Nitpick comments (4)
src/NLog/Internal/FileInfoHelper.cs (1)

50-57: Windows edge-case: extremely old (pre-1980) timestamps

The Year < 1980 fallback is disabled on Win32. On odd filesystems (e.g., certain SMB shares), CreationTimeUtc could be 1601-01-01 and propagate through, potentially skewing archive scheduling/cleanup. If intentional, consider a brief comment stating why Windows is excluded; otherwise, consider allowing the fallback when Year < 1980 regardless of OS.

src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs (1)

111-113: Remove stale MinValue sentinel branch

LookupValidFileCreationTimeUtc now always returns a non-null DateTime. The MinValue check is redundant and the else-path won’t trigger in practice.

-                    var fileBirthTimeUtc = FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo);
-                    var fileBirthTime = fileBirthTimeUtc != DateTime.MinValue ? NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc) : OpenStreamTime;
+                    var fileBirthTimeUtc = FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo);
+                    var fileBirthTime = NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc);
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

252-256: Log message label nit: variable is local-date based

currentDateUtc is actually local date (TimeSource.Current.Time.Date). Consider renaming the variable or log label for clarity. Optional.

src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1)

83-93: Harden against TOCTOU/IO exceptions when probing FileInfo (mirror FileLastModified’s try/catch)

CreationTime/Length can throw (e.g., racey deletes, permission issues). FileLastModified already logs and falls back; NextArchiveTime should do the same to avoid breaking logging on metadata failures.

@@
-                if (_nextArchiveTime < NLog.Time.TimeSource.Current.Time.AddMinutes(1) || _lastFileBirthTimeUtc == DateTime.MinValue)
-                {
-                    var fileInfo = new FileInfo(_filePath);
-                    var fileBirthTimeUtc = (fileInfo.Exists && fileInfo.Length != 0) ? FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo) : DateTime.MinValue;
-                    if (fileBirthTimeUtc == DateTime.MinValue || _lastFileBirthTimeUtc < fileBirthTimeUtc)
-                    {
-                        var fileBirthTime = fileBirthTimeUtc != DateTime.MinValue ? NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc) : OpenStreamTime;
-                        _nextArchiveTime = FileTarget.CalculateNextArchiveEventTime(_fileTarget.ArchiveEvery, fileBirthTime);
-                        _lastFileBirthTimeUtc = fileBirthTimeUtc;
-                    }
-                }
+                if (_nextArchiveTime < NLog.Time.TimeSource.Current.Time.AddMinutes(1) || _lastFileBirthTimeUtc == DateTime.MinValue)
+                {
+                    try
+                    {
+                        var fileInfo = new FileInfo(_filePath);
+                        var fileBirthTimeUtc = (fileInfo.Exists && fileInfo.Length != 0)
+                            ? FileInfoHelper.LookupValidFileCreationTimeUtc(fileInfo)
+                            : DateTime.MinValue;
+                        if (fileBirthTimeUtc == DateTime.MinValue || _lastFileBirthTimeUtc < fileBirthTimeUtc)
+                        {
+                            var fileBirthTime = fileBirthTimeUtc != DateTime.MinValue ? NLog.Time.TimeSource.Current.FromSystemTime(fileBirthTimeUtc) : OpenStreamTime;
+                            _nextArchiveTime = FileTarget.CalculateNextArchiveEventTime(_fileTarget.ArchiveEvery, fileBirthTime);
+                            _lastFileBirthTimeUtc = fileBirthTimeUtc;
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        NLog.Common.InternalLogger.Error(ex, "{0}: Failed to lookup FileInfo.CreationTimeUtc for file: {1}", _fileTarget, _filePath);
+                        if (ex.MustBeRethrown())
+                            throw;
+                        // Fall back to OpenStreamTime on probe failure
+                        if (_lastFileBirthTimeUtc != DateTime.MinValue)
+                        {
+                            _lastFileBirthTimeUtc = DateTime.MinValue;
+                            _nextArchiveTime = FileTarget.CalculateNextArchiveEventTime(_fileTarget.ArchiveEvery, OpenStreamTime);
+                        }
+                    }
+                }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf8a7ca and 9f77e91.

📒 Files selected for processing (4)
  • src/NLog/Internal/FileInfoHelper.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1)
src/NLog/Internal/FileInfoHelper.cs (3)
  • FileInfoHelper (39-81)
  • DateTime (41-44)
  • DateTime (46-60)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)
src/NLog/Internal/FileInfoHelper.cs (1)
  • FileInfoHelper (39-81)
src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs (1)
src/NLog/Internal/FileInfoHelper.cs (1)
  • FileInfoHelper (39-81)
src/NLog/Internal/FileInfoHelper.cs (2)
src/NLog/Targets/FileTarget.cs (2)
  • DateTime (837-855)
  • DateTime (865-873)
src/NLog/Internal/PlatformDetector.cs (1)
  • PlatformDetector (41-87)
🔇 Additional comments (3)
src/NLog/Internal/FileInfoHelper.cs (2)

41-44: API simplification to non-null DateTime looks good

Returning a non-null DateTime for LookupValidFileCreationTimeUtc simplifies all call sites. LGTM.


46-60: No nullable-pattern remnants for LookupValidFileCreationTimeUtc
Ran ripgrep for LookupValidFileCreationTimeUtc(...) ??, .HasValue, and != DateTime.MinValue—no matches found.

src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1)

86-86: Approve non-nullable migration for file birth time

No remaining ?? DateTime.MinValue fallbacks tied to LookupValidFileCreationTimeUtc; sentinel DateTime.MinValue is still only used for missing/empty files.

@snakefoot snakefoot force-pushed the LookupValidFileCreationTimeUtc branch 5 times, most recently from 659b4d4 to 25f138e Compare September 10, 2025 06:24
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 (2)
src/NLog/Internal/FileInfoHelper.cs (2)

50-55: Replace magic number 1980 with a named constant

This improves readability and documents intent (FAT epoch / “likely invalid birthtime”).

Apply within this block:

-            if (fileCreationTime.Year < 1980 && !PlatformDetector.IsWin32)
+            if (!PlatformDetector.IsWin32 && fileCreationTime.Year < MinReliableBirthYear)
             {
                 // Non-Windows-FileSystems doesn't always provide correct CreationTime/BirthTime
                 fileCreationTime = fallback(fileInfo);
-                if (finalFallback != null && fileCreationTime.Year < 1980)
+                if (finalFallback != null && fileCreationTime.Year < MinReliableBirthYear)
                 {
                     fileCreationTime = finalFallback(fileInfo);
                 }

And add near the top of the class:

// Consider values before this year as “unreliable” on non-Windows FS
private const int MinReliableBirthYear = 1980;

46-60: Remove unused finalFallback parameter and branch
The generic method is only ever called with two arguments (finalFallback always defaults to null), so the finalFallback check never runs. Drop the Func<T, DateTime>? finalFallback = null parameter and the inner if (finalFallback != null …) block.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f77e91 and 96dccc4.

📒 Files selected for processing (4)
  • src/NLog/Internal/FileInfoHelper.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs
  • src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/NLog/Internal/FileInfoHelper.cs (2)
src/NLog/Targets/FileTarget.cs (2)
  • DateTime (837-855)
  • DateTime (865-873)
src/NLog/Internal/PlatformDetector.cs (1)
  • PlatformDetector (41-87)
🔇 Additional comments (1)
src/NLog/Internal/FileInfoHelper.cs (1)

41-44: Non-nullable return: no nullable handling remains All callers now treat LookupValidFileCreationTimeUtc as a non-nullable DateTime (no usages of ??, ?., .Value, .HasValue, or DateTime? assignments).

@snakefoot snakefoot closed this Sep 10, 2025
auto-merge was automatically disabled September 10, 2025 16:01

Pull request was closed

@snakefoot snakefoot reopened this Sep 10, 2025
@snakefoot snakefoot closed this Sep 10, 2025
@snakefoot snakefoot reopened this Sep 10, 2025
@snakefoot snakefoot force-pushed the LookupValidFileCreationTimeUtc branch from 25f138e to e811ebd Compare September 10, 2025 16:57
@pull-request-size pull-request-size bot added size/M and removed size/S labels Sep 10, 2025
@snakefoot snakefoot force-pushed the LookupValidFileCreationTimeUtc branch from e811ebd to 864ae1f Compare September 10, 2025 17:04
@snakefoot snakefoot closed this Sep 10, 2025
@snakefoot snakefoot reopened this Sep 10, 2025
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

🧹 Nitpick comments (3)
src/NLog/Internal/FileInfoHelper.cs (1)

41-41: Name the threshold as a DateTime to avoid repeated Year checks (optional)

Consider adding a MinReliableBirthDateUtc = new DateTime(1980,1,1,0,0,0,DateTimeKind.Utc) and comparing with < MinReliableBirthDateUtc to improve intent and avoid magic number/year comparisons.

- internal const int MinReliableBirthYear = 1980;
+ internal const int MinReliableBirthYear = 1980;
+ internal static readonly DateTime MinReliableBirthDateUtc = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);

And then:

- if (fileCreationTime.Year < MinReliableBirthYear && !PlatformDetector.IsWin32)
+ if (fileCreationTime < MinReliableBirthDateUtc && !PlatformDetector.IsWin32)
src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1)

86-86: Minor: guard branch can avoid unnecessary FileInfo allocation

You allocate FileInfo regardless; micro-opt: check _lastFileBirthTimeUtc/date guard first, then allocate only when needed. Low impact, feel free to ignore.

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

1105-1113: Make the extra 500ms wait conditional (optional)

After the polling loop, the unconditional Thread.Sleep(500) lengthens the test. Sleep only if the loop exhausted all attempts, or switch to SpinWait.SpinUntil with a timeout.

-                        for (int i = 0; i < 100; ++i)
+                        var completed = false;
+                        for (int i = 0; i < 100; ++i)
                         {
                             var fileInfo = new FileInfo(logFile);
                             if (fileInfo.Exists && fileInfo.Length > 0)
-                                break;
+                            { completed = true; break; }
                             Thread.Sleep(50);
                         }
-                        Thread.Sleep(500);
+                        if (!completed)
+                            Thread.Sleep(500);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96dccc4 and 864ae1f.

📒 Files selected for processing (5)
  • src/NLog/Internal/FileInfoHelper.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1 hunks)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (2 hunks)
  • tests/NLog.UnitTests/Targets/FileTargetTests.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileAppenders/ExclusiveFileLockingAppender.cs
🧰 Additional context used
🧬 Code graph analysis (2)
src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (2)
src/NLog/Internal/FileInfoHelper.cs (3)
  • FileInfoHelper (39-83)
  • DateTime (43-46)
  • DateTime (48-62)
src/NLog/Targets/FileTarget.cs (2)
  • DateTime (837-855)
  • DateTime (865-873)
src/NLog/Internal/FileInfoHelper.cs (2)
src/NLog/Targets/FileTarget.cs (2)
  • DateTime (837-855)
  • DateTime (865-873)
src/NLog/Internal/PlatformDetector.cs (1)
  • PlatformDetector (41-87)
🔇 Additional comments (2)
src/NLog/Internal/FileInfoHelper.cs (2)

41-62: Non-null return + year-threshold fallback looks good

The simplification reads well and keeps behavior consistent with non-Windows fallbacks. No functional concerns spotted here.


43-47: No nullable-dependent usages of LookupValidFileCreationTimeUtc remain. Verified all call sites; none expect a null value.

@sonarqubecloud
Copy link

@snakefoot snakefoot merged commit acf85d7 into NLog:dev Sep 10, 2025
6 checks passed
@snakefoot snakefoot changed the title FileInfoHelper LookupValidFileCreationTimeUtc - Removed nullable DateTime FileTarget - Better fallback logic when missing file system birthtime Sep 10, 2025
This was referenced Sep 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

file-archiving Issues with archiving with the file target file-target refactoring size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant