Add lifecycle hooks to file targets#6215
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds ChangesFile lifecycle hooks feature
Process name renderer test update
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/NLog/Targets/FileLifecycleHooks.cs (1)
87-93: ⚡ Quick winAdd XML documentation for the return value.
The
<returns>XML documentation tag is missing for theCreateLinkedHooksmethod.📝 Suggested documentation addition
/// Creates a linked hook combining the current and given <see cref="FileLifecycleHooks"/>. /// </summary> /// <param name="hooks">Hooks to link with current <see cref="FileLifecycleHooks"/>. Methods on <paramref name="hooks"/> will be invoked after the current <see cref="FileLifecycleHooks"/> methods.</param> -/// <returns></returns> +/// <returns>A new <see cref="LinkedFileLifecycleHooks"/> that invokes both hook instances in sequence.</returns> public FileLifecycleHooks CreateLinkedHooks(FileLifecycleHooks hooks) => new LinkedFileLifecycleHooks(this, hooks);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/NLog/Targets/FileLifecycleHooks.cs` around lines 87 - 93, The CreateLinkedHooks method lacks a <returns> XML doc; update the XML documentation for the CreateLinkedHooks(FileLifecycleHooks hooks) method to include a <returns> tag that clearly states it returns a new LinkedFileLifecycleHooks instance that links the current FileLifecycleHooks with the provided hooks (methods on the provided hooks will be invoked after the current ones), referencing LinkedFileLifecycleHooks and FileLifecycleHooks in the description.tests/NLog.UnitTests/Targets/FileTargetTests.cs (1)
150-184: ⚡ Quick winStrengthen error-path verification by asserting log output was still written.
Hooks_ErrorHandlingTestvalidates callback recording, but it should also assert that writing still succeeded when hooks throw.Suggested test addition
LogManager.Configuration = null; // Flush +Assert.True(new FileInfo(logFile).Length > 0); + var expected = new List<string> {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/NLog.UnitTests/Targets/FileTargetTests.cs` around lines 150 - 184, The test Hooks_ErrorHandlingTest currently only asserts the hook callback sequence; enhance it to also verify that the log entry was actually written despite hooks throwing by reading the temporary logFile (after the existing flush: LogManager.Configuration = null) and asserting the file contains the expected logged text (e.g., "ccc" or the formatted log line); update the method to use the existing logFile and callRecording variables and add a final assertion like Assert.Contains("ccc", File.ReadAllText(logFile)) before cleanup.tests/NLog.UnitTests/Targets/LinkedFileLifecycleHooksTests.cs (1)
98-139: ⚡ Quick winExtract shared test logic to eliminate duplication.
CreateLinkedHooksandVerifyChainedCallsshare nearly identical test logic (lines 107-138 duplicate lines 64-95). The only difference is how the target hook chain is constructed (line 105 vs line 62).Consider extracting the shared assertion logic into a helper method:
private void VerifyHookChaining(FileLifecycleHooks target, List<string> callRecording) { target.OnFileDeleting("del.log"); target.OnFileClosed("close.log"); using (var stream = new MemoryStream()) { var actualStream = target.OnFileOpened("open.log", stream); Assert.IsType<MockStream>(actualStream); Assert.Equal("hook_2", ((MockStream)actualStream).Name); } var expectedTarget = new FileTarget("my-file_1"); target.OnTargetInitialize(expectedTarget); expectedTarget = new FileTarget("my-file_2"); expectedTarget.Dispose(); target.OnTargetClose(expectedTarget); var expected = new List<string> { "hook_1_OnFileDeleting_del.log", "hook_2_OnFileDeleting_del.log", "hook_1_OnFileClosed_close.log", "hook_2_OnFileClosed_close.log", "hook_1_OnFileOpened_open.log_stream", "hook_2_OnFileOpened_open.log_hook_1", "hook_1_OnTargetInitialize_my-file_1", "hook_2_OnTargetInitialize_my-file_1", "hook_1_OnTargetClose_my-file_2", "hook_2_OnTargetClose_my-file_2" }; Assert.Equal(expected, callRecording); }Then both tests become:
[Fact] public void VerifyChainedCalls() { var callRecording = new List<string>(); var first = new MockFileLifecycleHooks("hook_1", callRecording); var second = new MockFileLifecycleHooks("hook_2", callRecording); var target = new LinkedFileLifecycleHooks(first, second); VerifyHookChaining(target, callRecording); } [Fact] public void CreateLinkedHooks() { var callRecording = new List<string>(); var first = new MockFileLifecycleHooks("hook_1", callRecording); var second = new MockFileLifecycleHooks("hook_2", callRecording); var target = first.CreateLinkedHooks(second); VerifyHookChaining(target, callRecording); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/NLog.UnitTests/Targets/LinkedFileLifecycleHooksTests.cs` around lines 98 - 139, Tests CreateLinkedHooks and VerifyChainedCalls duplicate the same sequence of actions and assertions; extract that shared logic into a private helper (e.g., VerifyHookChaining(FileLifecycleHooks target, List<string> callRecording)) that performs the OnFileDeleting/OnFileClosed/OnFileOpened/OnTargetInitialize/OnTargetClose calls and asserts the expected callRecording; then simplify both tests to construct MockFileLifecycleHooks("hook_1", callRecording) and MockFileLifecycleHooks("hook_2", callRecording) and call VerifyHookChaining with either new LinkedFileLifecycleHooks(first, second) or first.CreateLinkedHooks(second).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/NLog.sln.DotSettings`:
- Around line 2-3: The PR includes two ReSharper inspection settings (keys
BuiltInTypeReferenceStyle and BuiltInTypeReferenceStyleForMemberAccess) set to
DO_NOT_SHOW in src/NLog.sln.DotSettings that look accidental; either remove
those two <s:String> entries from the commit if they were pulled from your local
IDE, or replace them with a short commit message and PR description confirming
this is an intentional team-wide style change (and keep the entries). Locate the
entries by their keys
("/Default/CodeInspection/Highlighting/InspectionSeverities/=BuiltInTypeReferenceStyle/@EntryIndexedValue"
and
"/Default/CodeInspection/Highlighting/InspectionSeverities/=BuiltInTypeReferenceStyleForMemberAccess/@EntryIndexedValue")
and act accordingly.
In `@src/NLog.Targets.GZipFile/GZipFileTarget.cs`:
- Around line 65-71: The new GZipFileTarget(FileLifecycleHooks hooks)
constructor doesn't set ArchiveOldFileOnStartup to true, causing
InitializeTarget to throw NLogConfigurationException; update the constructor
(GZipFileTarget(FileLifecycleHooks hooks)) to initialize ArchiveOldFileOnStartup
= true (matching the parameterless GZipFileTarget()) so instances created via
this overload behave the same and won't fail during InitializeTarget.
In `@src/NLog/Targets/FileTarget.cs`:
- Around line 584-592: The hook catch blocks (e.g., the one around
Hooks.OnTargetInitialize and the similar blocks for OnTargetClose, OnFileClosed,
and OnFileOpened) currently swallow all Exception instances; update each catch
to preserve NLog’s fatal-exception behavior by invoking the same guard used
elsewhere (call ExceptionHelpers.MustBeRethrown(ex) or equivalent) and rethrow
when it returns true, otherwise log via InternalLogger.Error as today—apply this
pattern to the catch blocks for OnTargetInitialize, OnTargetClose, OnFileClosed
and OnFileOpened so fatal exceptions are not swallowed.
- Around line 1407-1418: The call to Hooks.OnFileOpened can return null and
currently overwrites the original FileStream, risking lost references and leaks;
modify the FileTarget logic to keep a reference to the original FileStream
(stream/originalStream), call var replacement = Hooks.OnFileOpened(filePath,
originalStream), then: if replacement is null, log a warning (use
InternalLogger.Warn/Error) and continue using originalStream; if replacement is
non-null and not the same instance, dispose/close the originalStream to avoid a
leak and use replacement; if replacement equals originalStream, just use it.
Also preserve the existing exception catch for hook throws
(FileLifecycleHooks.OnFileOpened, stream variable, Hooks).
---
Nitpick comments:
In `@src/NLog/Targets/FileLifecycleHooks.cs`:
- Around line 87-93: The CreateLinkedHooks method lacks a <returns> XML doc;
update the XML documentation for the CreateLinkedHooks(FileLifecycleHooks hooks)
method to include a <returns> tag that clearly states it returns a new
LinkedFileLifecycleHooks instance that links the current FileLifecycleHooks with
the provided hooks (methods on the provided hooks will be invoked after the
current ones), referencing LinkedFileLifecycleHooks and FileLifecycleHooks in
the description.
In `@tests/NLog.UnitTests/Targets/FileTargetTests.cs`:
- Around line 150-184: The test Hooks_ErrorHandlingTest currently only asserts
the hook callback sequence; enhance it to also verify that the log entry was
actually written despite hooks throwing by reading the temporary logFile (after
the existing flush: LogManager.Configuration = null) and asserting the file
contains the expected logged text (e.g., "ccc" or the formatted log line);
update the method to use the existing logFile and callRecording variables and
add a final assertion like Assert.Contains("ccc", File.ReadAllText(logFile))
before cleanup.
In `@tests/NLog.UnitTests/Targets/LinkedFileLifecycleHooksTests.cs`:
- Around line 98-139: Tests CreateLinkedHooks and VerifyChainedCalls duplicate
the same sequence of actions and assertions; extract that shared logic into a
private helper (e.g., VerifyHookChaining(FileLifecycleHooks target, List<string>
callRecording)) that performs the
OnFileDeleting/OnFileClosed/OnFileOpened/OnTargetInitialize/OnTargetClose calls
and asserts the expected callRecording; then simplify both tests to construct
MockFileLifecycleHooks("hook_1", callRecording) and
MockFileLifecycleHooks("hook_2", callRecording) and call VerifyHookChaining with
either new LinkedFileLifecycleHooks(first, second) or
first.CreateLinkedHooks(second).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 5e4fa9f5-77d0-4bb7-928a-92fdcbc88e2c
📒 Files selected for processing (11)
src/NLog.Targets.AtomicFile/AtomicFileTarget.cssrc/NLog.Targets.GZipFile/GZipFileTarget.cssrc/NLog.slnsrc/NLog.sln.DotSettingssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileLifecycleHooks.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LinkedFileLifecycleHooks.cstests/NLog.Targets.AtomicFile.Tests/AtomicFileTests.cstests/NLog.UnitTests/Targets/FileTargetTests.cstests/NLog.UnitTests/Targets/LinkedFileLifecycleHooksTests.cs
|
I guess those 2 additional comments suddenly exploded into a lot more. But thank you for your quick resolve. I'm happy about the result, and the Sonar-Report also looks good. Do you have anything else that you want to add? And have you verified that the new FileTarget API provides what you need?
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/NLog/Targets/FileTarget.cs (1)
607-610:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve fatal-exception behavior in lifecycle hook catch blocks.
Line 607, Line 1015, Line 1039, and Line 1346 catch
Exceptionand always continue. AddExceptionMustBeRethrown(ex)guards before logging so fatal exceptions are not swallowed.🛠️ Proposed fix pattern (apply to all four hook catch blocks)
catch (Exception ex) { + if (ExceptionMustBeRethrown(ex)) + throw; InternalLogger.Error(ex, "{0}: {1} hook threw an unexpected error", this, nameof(FileLifecycleHooks.OnTargetClose)); }catch (Exception ex) { + if (ExceptionMustBeRethrown(ex)) + throw; InternalLogger.Error(ex, "{0}: {1} hook threw an unexpected error for file '{2}'", this, nameof(FileLifecycleHooks.OnFileDeleting), filepath); }catch (Exception ex) { + if (ExceptionMustBeRethrown(ex)) + throw; InternalLogger.Error(ex, "{0}: {1} hook threw an unexpected error for file '{2}'", this, nameof(FileLifecycleHooks.OnFileClosed), openFile.FileAppender.FilePath); }catch (Exception ex) { + if (ExceptionMustBeRethrown(ex)) + throw; InternalLogger.Error(ex, "{0}: {1} hook threw an unexpected error for file '{2}'", this, nameof(FileLifecycleHooks.OnFileOpened), filePath); }Also applies to: 1015-1018, 1039-1042, 1346-1349
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/NLog/Targets/FileTarget.cs` around lines 607 - 610, In the FileTarget.cs file, add `ExceptionMustBeRethrown(ex)` guard checks before logging in all four lifecycle hook catch blocks (the OnTargetClose hook catch block at line 607, and the other three hook catch blocks at lines 1015, 1039, and 1346). Each catch block should check if the exception is fatal using `ExceptionMustBeRethrown(ex)` before calling InternalLogger.Error, and rethrow the exception if it is fatal, ensuring fatal exceptions are not swallowed by these catch blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/NLog/Targets/FileLifecycleHooks.cs`:
- Around line 48-49: The XML documentation comments in the FileLifecycleHooks
class contain two typos that affect generated API docs. On line 48 in the
OnFileClosed documentation, add a missing space between the word "that" and the
opening tag "<see" (currently reads "that<see" instead of "that <see").
Additionally, on line 102, correct the spelling error by changing "beeing" to
"being" in the corresponding documentation comment. These are simple text
corrections within the XML doc comment blocks.
---
Duplicate comments:
In `@src/NLog/Targets/FileTarget.cs`:
- Around line 607-610: In the FileTarget.cs file, add
`ExceptionMustBeRethrown(ex)` guard checks before logging in all four lifecycle
hook catch blocks (the OnTargetClose hook catch block at line 607, and the other
three hook catch blocks at lines 1015, 1039, and 1346). Each catch block should
check if the exception is fatal using `ExceptionMustBeRethrown(ex)` before
calling InternalLogger.Error, and rethrow the exception if it is fatal, ensuring
fatal exceptions are not swallowed by these catch blocks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bef176a2-c03a-4077-b7c0-da9762151f7a
📒 Files selected for processing (5)
src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileLifecycleHooks.cssrc/NLog/Targets/FileTarget.cstests/NLog.UnitTests/LayoutRenderers/ProcessNameLayoutRendererTests.cstests/NLog.UnitTests/Targets/FileTargetTests.cs
Thank you very much for your time and frequent reviews. I've tested the new behavior with the package from the build and it works for my use case. I don't have anything I would like to add to this PR. |
|
I usually make a NLog patch-releases once a month (like now), and postpone bigger features for the minor-releases once a year (like 5-7 month from now) How urgent is this feature for you? |
Of course, I would like to have access to this feature as fast as possible, but it’s not that urgent. 5-7 Months work for me 😊 |
|
Then I will release NLog v6.2 after my summer holiday around 1st August. |
PR for #6213