-
Notifications
You must be signed in to change notification settings - Fork 1.4k
FileTarget - Rolling to next directory should not fail #5998
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
WalkthroughExtracted DirectoryNotFoundException handling from CreateFileStreamWithRetry into a private helper CreateFileStreamWithDirectory, preserving retry-and-create-directory behavior; updated a unit test to write into a GUID-nested subdirectory, exercise directory creation, and assert created directories. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Caller
participant FT as FileTarget
participant FS as OpenNewFileStream
participant IO as Directory
Caller->>FT: CreateFileStreamWithRetry(filePath, bufferSize, initialFileOpen)
FT->>FT: CreateFileStreamWithDirectory(filePath, bufferSize, initialFileOpen)
FT->>FS: OpenNewFileStream(filePath, bufferSize, initialFileOpen)
alt File opened
FS-->>FT: FileStream
FT-->>Caller: FileStream
else DirectoryNotFoundException
FT->>FT: if CreateDirs==false -> rethrow
alt CreateDirs==true
FT->>IO: CreateDirectory(directoryName)
IO-->>FT: success / throws
alt CreateDirectory succeeded
FT->>FS: OpenNewFileStream(filePath, bufferSize, initialFileOpen) (retry)
FS-->>FT: FileStream
FT-->>Caller: FileStream
else CreateDirectory failed
FT-->>Caller: throw NLogRuntimeException(IO exception)
end
end
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Pre-merge checks (5 passed)✅ Passed checks (5 passed)
Poem
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: 0
🧹 Nitpick comments (4)
tests/NLog.UnitTests/Targets/FileTargetTests.cs (3)
1019-1019: Assert files and contents to catch “first message lost”.To ensure no event is dropped when creating directories, verify both files exist and contain “a” and “b”.
var logger = LogManager.GetLogger("A"); logger.Info("a"); logger.Info("b"); Assert.Equal(createDirs, Directory.Exists(tempDir)); if (createDirs) - Assert.Equal(2, Directory.GetDirectories(tempDir).Length); + { + Assert.Equal(2, Directory.GetDirectories(tempDir).Length); + var files = Directory.GetFiles(tempDir, "log.log", SearchOption.AllDirectories); + Assert.Equal(2, files.Length); + var contents = files.Select(f => File.ReadAllText(f).Trim()).OrderBy(s => s).ToArray(); + Assert.Equal(new[] { "a", "b" }, contents); + } + else + { + Assert.Empty(Directory.GetDirectories(tempDir)); + Assert.Empty(Directory.GetFiles(tempDir, "log.log", SearchOption.AllDirectories)); + }
1001-1001: Restore ThrowExceptions to its prior value.Avoid leaking global state to other tests.
- LogManager.ThrowExceptions = false; + var prevThrow = LogManager.ThrowExceptions; + LogManager.ThrowExceptions = false;And in finally:
- LogManager.ThrowExceptions = true; + LogManager.ThrowExceptions = prevThrow;
1029-1030: Remove ineffective file cleanup.
logfilecontains${guid}and won’t match actual files. Directory cleanup below already removes them.- if (File.Exists(logfile)) - File.Delete(logfile);src/NLog/Targets/FileTarget.cs (1)
1231-1259: Guard against null/empty directory names before CreateDirectory.
Path.GetDirectoryName(filePath)can be null/empty in edge cases; be defensive to avoidArgumentNullException.- var directoryName = Path.GetDirectoryName(filePath); - - try - { - Directory.CreateDirectory(directoryName); - } + var directoryName = Path.GetDirectoryName(filePath); + try + { + if (string.IsNullOrEmpty(directoryName)) + throw; // No directory part to create; rethrow original DirectoryNotFoundException + Directory.CreateDirectory(directoryName); + } catch (Exception ex) { // If creating a directory failed, don't retry for this message throw new NLogRuntimeException($"Could not create directory {directoryName}", ex); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/NLog/Targets/FileTarget.cs(2 hunks)tests/NLog.UnitTests/Targets/FileTargetTests.cs(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/NLog/Targets/FileTarget.cs (3)
src/NLog.Targets.GZipFile/GZipFileTarget.cs (1)
Stream(88-103)src/NLog/Common/InternalLogger.cs (1)
InternalLogger(51-511)src/NLog/NLogRuntimeException.cs (4)
NLogRuntimeException(51-53)NLogRuntimeException(59-62)NLogRuntimeException(83-86)NLogRuntimeException(100-103)
🔇 Additional comments (2)
tests/NLog.UnitTests/Targets/FileTargetTests.cs (1)
997-997: Good: exercising directory creation with dynamic ${guid}.This reliably forces different directories per write and covers the reported scenario.
src/NLog/Targets/FileTarget.cs (1)
1212-1212: LGTM: refactor improves readability and reusability.Delegating to a helper keeps retry logic focused.
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 (1)
src/NLog/Targets/FileTarget.cs (1)
1231-1258: Harden directory creation: guard null dir, preserve retry on transient IO, fix log text.Two small robustness tweaks:
- Handle Path.GetDirectoryName returning null/empty (rare but possible) to avoid wrapping an ArgumentNullException and to let the outer retry handle it.
- If Directory.CreateDirectory throws an IOException (transient/lock/share issues), rethrow it so CreateFileStreamWithRetry’s retry-loop can kick in. Keep NLogRuntimeException for non-IO errors.
- Nit: log says “FileName”; it’s a path.
Apply:
- InternalLogger.Debug("{0}: DirectoryNotFoundException - Attempting to create directory for FileName: {1}", this, filePath); + InternalLogger.Debug("{0}: DirectoryNotFoundException - Attempting to create directory for FilePath: {1}", this, filePath); - var directoryName = Path.GetDirectoryName(filePath); + var directoryName = Path.GetDirectoryName(filePath); + if (string.IsNullOrEmpty(directoryName)) + { + // Nothing to create; let outer retry-logic decide. + throw; + } try { - Directory.CreateDirectory(directoryName); + Directory.CreateDirectory(directoryName); } - catch (Exception ex) + catch (IOException) { - // If creating a directory failed, don't retry for this message - throw new NLogRuntimeException($"Could not create directory {directoryName}", ex); + // Allow the outer retry-loop in CreateFileStreamWithRetry to handle transient IO failures + throw; + } + catch (Exception ex) + { + // Non-IO errors won't benefit from retry; keep current behavior + throw new NLogRuntimeException($"Could not create directory '{directoryName}' for '{filePath}'", ex); }Would you like me to add/adjust a unit test that asserts the very first write succeeds when the directory does not exist (with minimal race via multiple concurrent loggers)?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/NLog/Targets/FileTarget.cs(2 hunks)tests/NLog.UnitTests/Targets/FileTargetTests.cs(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/NLog.UnitTests/Targets/FileTargetTests.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/NLog/Targets/FileTarget.cs (3)
src/NLog.Targets.GZipFile/GZipFileTarget.cs (1)
Stream(88-103)src/NLog/Common/InternalLogger.cs (1)
InternalLogger(51-511)src/NLog/NLogRuntimeException.cs (4)
NLogRuntimeException(51-53)NLogRuntimeException(59-62)NLogRuntimeException(83-86)NLogRuntimeException(100-103)
🔇 Additional comments (1)
src/NLog/Targets/FileTarget.cs (1)
1212-1212: Good extraction and delegation.Refactoring the DirectoryNotFoundException path into a helper keeps CreateFileStreamWithRetry lean and preserves the retry semantics.
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 (1)
src/NLog/Targets/FileTarget.cs (1)
1231-1258: Guard null/empty directory names and preserve original exception; improve debug contextIf Path.GetDirectoryName(filePath) returns null/empty, Directory.CreateDirectory will throw ArgumentNullException, masking the original DirectoryNotFoundException. Also, logging the target directory aids diagnostics.
Apply:
- private Stream CreateFileStreamWithDirectory(string filePath, int bufferSize, bool initialFileOpen) + private Stream CreateFileStreamWithDirectory(string filePath, int bufferSize, bool initialFileOpen) { try { return OpenNewFileStream(filePath, bufferSize, initialFileOpen); } - catch (DirectoryNotFoundException) + catch (DirectoryNotFoundException dnfe) { if (!CreateDirs) throw; - InternalLogger.Debug("{0}: DirectoryNotFoundException - Attempting to create directory for file: {1}", this, filePath); - - var directoryName = Path.GetDirectoryName(filePath); + var directoryName = Path.GetDirectoryName(filePath); + if (string.IsNullOrEmpty(directoryName)) + { + // No directory component to create; keep original stack/exception. + throw; + } + InternalLogger.Debug("{0}: DirectoryNotFoundException - Attempting to create directory: {1} for file: {2}", this, directoryName, filePath); try { Directory.CreateDirectory(directoryName); } catch (Exception ex) { - // If creating a directory failed, don't retry for this message - throw new NLogRuntimeException($"Could not create directory {directoryName}", ex); + // If creating a directory failed, don't retry for this message + throw new NLogRuntimeException($"Could not create directory '{directoryName}' for file '{filePath}'", ex); } return OpenNewFileStream(filePath, bufferSize, initialFileOpen); } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/NLog/Targets/FileTarget.cs(2 hunks)tests/NLog.UnitTests/Targets/FileTargetTests.cs(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/NLog.UnitTests/Targets/FileTargetTests.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/NLog/Targets/FileTarget.cs (3)
src/NLog.Targets.GZipFile/GZipFileTarget.cs (1)
Stream(88-103)src/NLog/Common/InternalLogger.cs (1)
InternalLogger(51-511)src/NLog/NLogRuntimeException.cs (4)
NLogRuntimeException(51-53)NLogRuntimeException(59-62)NLogRuntimeException(83-86)NLogRuntimeException(100-103)
🔇 Additional comments (1)
src/NLog/Targets/FileTarget.cs (1)
1212-1212: Nice extraction: delegate to a directory-aware stream openerMoving DirectoryNotFoundException handling into a helper simplifies the retry loop and keeps behavior intact.
|



Resolves #5997