-
Notifications
You must be signed in to change notification settings - Fork 1.4k
FileTarget - Improve fallback logic when missing file system birthtime #5996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughFileInfoHelper.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
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
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
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.
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)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this 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 policyTo 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) timestampsThe 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 branchLookupValidFileCreationTimeUtc 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 basedcurrentDateUtc 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
📒 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 goodReturning a non-null DateTime for LookupValidFileCreationTimeUtc simplifies all call sites. LGTM.
46-60: No nullable-pattern remnants for LookupValidFileCreationTimeUtc
Ran ripgrep forLookupValidFileCreationTimeUtc(...) ??,.HasValue, and!= DateTime.MinValue—no matches found.src/NLog/Targets/FileAppenders/MinimalFileLockingAppender.cs (1)
86-86: Approve non-nullable migration for file birth timeNo remaining
?? DateTime.MinValuefallbacks tied toLookupValidFileCreationTimeUtc; sentinelDateTime.MinValueis still only used for missing/empty files.
659b4d4 to
25f138e
Compare
There was a problem hiding this 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 constantThis 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 thefinalFallbackcheck never runs. Drop theFunc<T, DateTime>? finalFallback = nullparameter and the innerif (finalFallback != null …)block.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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, orDateTime?assignments).
Pull request was closed
25f138e to
e811ebd
Compare
e811ebd to
864ae1f
Compare
There was a problem hiding this 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 allocationYou 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
📒 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 goodThe 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.
|



No description provided.