Skip to content

NLog.Targets.AtomicFile supporting NetStandard2 with Win32 API#6100

Merged
snakefoot merged 5 commits intoNLog:devfrom
michailf:netstandard2.0_for_AtomicFile-tests
Feb 28, 2026
Merged

NLog.Targets.AtomicFile supporting NetStandard2 with Win32 API#6100
snakefoot merged 5 commits intoNLog:devfrom
michailf:netstandard2.0_for_AtomicFile-tests

Conversation

@michailf
Copy link
Contributor

Unit tests for netstandard2.0 target for NLog AtomicFile target.

Stacked on top of #6099

@welcome
Copy link

welcome bot commented Feb 26, 2026

Thanks for opening this pull request!
We will try to review this soon! Please note that pull requests with unit tests are earlier accepted 👼

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Walkthrough

Adds netstandard2.0 support and a NETSTANDARD-specific Windows atomic-file creation path by introducing Win32 CreateFile interop via a new NativeMethods helper, updating conditional compilation in AtomicFileTarget, adjusting project files, and adding test/script changes; no public API signatures were changed.

Changes

Cohort / File(s) Summary
Windows interop & core logic
src/NLog.Targets.AtomicFile/AtomicFileTarget.cs, src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs
Adds a NETSTANDARD-only CreateWindowsStream that P/Invokes Win32 CreateFile via new NativeMethods and Win32 enums; routes NETSTANDARD atomic-file creation through this interop path while keeping NETFRAMEWORK and Unix branches unchanged.
Project configuration
src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj
Adds netstandard2.0 to TargetFrameworks, removes explicit net45 group, and merges/extends PackageReference conditions so Mono.Posix.NETStandard applies to net8.0 and netstandard2.0.
Tests & CI script
run-tests.ps1, tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj
Adds netstandard2.0 test invocation to run-tests.ps1 and a conditional test-project reference/hint path so tests can target the built NETSTANDARD artifact.
Conditional compilation adjustments
src/NLog.Targets.AtomicFile/... (files with #if NETSTANDARD / #if NETFRAMEWORK)
Reorganizes conditional compilation blocks to separate the NETSTANDARD Windows-specific implementation from existing NETFRAMEWORK and Unix implementations and updates end-of-conditional comments.

Sequence Diagram(s)

sequenceDiagram
  participant Atomic as AtomicFileTarget
  participant Native as NativeMethods (P/Invoke)
  participant OS as Windows Kernel
  participant Stream as FileStream

  Atomic->>Native: Request CreateFile handle (path, access, flags)
  Native->>OS: Call CreateFileW (lpFileName, access, share, disp, flags)
  OS-->>Native: Return SafeFileHandle
  alt handle valid
    Native-->>Atomic: SafeFileHandle
    Atomic->>Stream: Create FileStream from handle (no buffering)
    Stream-->>Atomic: FileStream ready
  else handle invalid
    Native-->>Atomic: error/invalid handle
    Atomic->>Atomic: fallback or throw
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped in with a tiny cheer,
P/Invoke ears and code so near,
CreateFile lent a careful stream,
NetStandard paths now shine and gleam,
Files snug and safe — a bunny beam! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Netstandard2.0 for atomic file tests' is directly related to the PR's main objective of adding unit tests for the .NET Standard 2.0 target of the NLog AtomicFile, matching the stated purpose.
Description check ✅ Passed The description 'Unit tests for netstandard2.0 target for NLog AtomicFile target' is related to the changeset, which includes test infrastructure, test project configurations, and supporting implementation changes for netstandard2.0 testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 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

🧹 Nitpick comments (1)
src/NLog.Targets.AtomicFile/AtomicFileTarget.cs (1)

123-136: The SetAccessControl call appears ineffective; consider removing or fixing.

The FileSecurity object created on line 123 has no access rules added to it. Calling SetAccessControl(fileSecurity) on line 136 with this empty security descriptor doesn't actually inherit permissions from the parent directory—it does nothing meaningful.

If the intent is to inherit parent ACLs, the file should already inherit them automatically when created on NTFS (via the fileSecurity parameter passed to Create). The SetAccessRuleProtection(false, false) call ensures inheritance is not blocked, but calling SetAccessControl afterward with the same empty object is redundant.

Consider either:

  1. Removing line 136 and the misleading comment on line 135, or
  2. Actually retrieving and applying the parent directory's ACL if explicit inheritance copying is needed.
♻️ Suggested simplification
             fileStream = fileInfo.Create(
                 FileMode.CreateNew,
                 systemRights,
                 fileShare,
                 bufferSize: 1,
                 FileOptions.None,
                 fileSecurity: fileSecurity);
-
-            // inherit permissions from parent directory
-            fileInfo.SetAccessControl(fileSecurity);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs` around lines 123 - 136, The
call to fileInfo.SetAccessControl(fileSecurity) in AtomicFileTarget.cs is
redundant because the FileSecurity instance has no rules and
SetAccessRuleProtection(false, false) merely allows inheritance; either remove
the SetAccessControl call and the misleading "inherit permissions" comment, or
if explicit copying is required, fetch the parent directory's FileSecurity
(e.g., DirectoryInfo.GetAccessControl()) and apply that object to the file
instead; update the code around fileInfo.Create(...) and the comment near
SetAccessControl to reflect the chosen approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.NetStandard2.Tests.csproj`:
- Line 29: The PackageReference Include value "Basic.Reference.Assemblies "
contains a trailing space; open the project file and edit the PackageReference
element to remove the trailing space so the Include reads
"Basic.Reference.Assemblies" (verify there are no other accidental trailing
spaces in package names), save the .csproj and run a restore/build to confirm
the package resolves correctly.

---

Nitpick comments:
In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs`:
- Around line 123-136: The call to fileInfo.SetAccessControl(fileSecurity) in
AtomicFileTarget.cs is redundant because the FileSecurity instance has no rules
and SetAccessRuleProtection(false, false) merely allows inheritance; either
remove the SetAccessControl call and the misleading "inherit permissions"
comment, or if explicit copying is required, fetch the parent directory's
FileSecurity (e.g., DirectoryInfo.GetAccessControl()) and apply that object to
the file instead; update the code around fileInfo.Create(...) and the comment
near SetAccessControl to reflect the chosen approach.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1594d92 and 9f0276b.

📒 Files selected for processing (4)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj
  • src/NLog.sln
  • tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.NetStandard2.Tests.csproj

@michailf michailf marked this pull request as draft February 27, 2026 00:04
@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch 4 times, most recently from 79091b9 to d1a72d9 Compare February 27, 2026 06:43
@michailf michailf marked this pull request as ready for review February 27, 2026 11:39
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 (1)
src/NLog.Targets.AtomicFile/Internal/Win32FileAttributes.cs (1)

44-45: Consider making the enum internal to match its namespace intent.

The Win32FileAttributes enum is declared as public but resides in the NLog.Internal namespace. Since this is an implementation detail for the Win32 interop layer, marking it internal would prevent it from being part of the public API surface.

♻️ Suggested change
     [Flags]
-    public enum Win32FileAttributes : int
+    internal enum Win32FileAttributes : int
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/Internal/Win32FileAttributes.cs` around lines 44
- 45, The Win32FileAttributes enum is declared public but lives in the
NLog.Internal namespace and should be an implementation detail; change its
accessibility from public to internal by updating the Win32FileAttributes enum
declaration to internal (ensure no external code relies on its public visibility
and update any unit tests or callers if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NLog.Targets.AtomicFile/Internal/Win32FileNativeMethods.cs`:
- Line 40: Remove the unused using directive "using NLog.Targets;" from the top
of Win32FileNativeMethods.cs—it's not referenced by the Win32FileNativeMethods
class or any methods in this file, so delete that import to clean up warnings
and unused references.

---

Nitpick comments:
In `@src/NLog.Targets.AtomicFile/Internal/Win32FileAttributes.cs`:
- Around line 44-45: The Win32FileAttributes enum is declared public but lives
in the NLog.Internal namespace and should be an implementation detail; change
its accessibility from public to internal by updating the Win32FileAttributes
enum declaration to internal (ensure no external code relies on its public
visibility and update any unit tests or callers if needed).

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f0276b and d1a72d9.

📒 Files selected for processing (7)
  • run-tests.ps1
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/Internal/Win32FileAttributes.cs
  • src/NLog.Targets.AtomicFile/Internal/Win32FileNativeMethods.cs
  • src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj
  • src/NLog.sln
  • tests/NLog.Targets.AtomicFile.Tests.NetStandard20/NLog.Targets.AtomicFile.Tests.NetStandard20.csproj
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/NLog.sln

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch from d1a72d9 to 4b63730 Compare February 27, 2026 13:58
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NLog.Targets.AtomicFile/Internal/Win32FileNativeMethods.cs`:
- Around line 43-48: The Win32SecurityFlags enum has an incorrect value for
SecurityAnonymous; update the enum in Win32FileNativeMethods.cs so
SecurityAnonymous is set to 0x00000000 (zero) instead of 0x00200000 to match the
Win32 SECURITY_ANONYMOUS constant; ensure any callers (e.g., CreateFile usage in
AtomicFileTarget.cs where Win32SecurityFlags is passed) will now receive the
correct anonymous security level.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1a72d9 and 4b63730.

📒 Files selected for processing (7)
  • run-tests.ps1
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/Internal/Win32FileAttributes.cs
  • src/NLog.Targets.AtomicFile/Internal/Win32FileNativeMethods.cs
  • src/NLog.Targets.AtomicFile/NLog.Targets.AtomicFile.csproj
  • src/NLog.sln
  • tests/NLog.Targets.AtomicFile.Tests.NetStandard20/NLog.Targets.AtomicFile.Tests.NetStandard20.csproj
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • tests/NLog.Targets.AtomicFile.Tests.NetStandard20/NLog.Targets.AtomicFile.Tests.NetStandard20.csproj
  • src/NLog.sln
  • run-tests.ps1

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch 2 times, most recently from dec15c8 to a88286e Compare February 27, 2026 18:36
@michailf
Copy link
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ Actions performed

Reviews resumed.

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch from a88286e to 9353306 Compare February 27, 2026 22:50
@snakefoot
Copy link
Contributor

snakefoot commented Feb 27, 2026

Really like to have unit-test of the Win32-API, maybe something similar to TestTargetFramework for NLog.UnitTests.

	dotnet test ./tests/NLog.Targets.AtomicFile.Tests/ --configuration release /p:TestTargetFramework=netstandard2.0
	if (-Not $LastExitCode -eq 0)
		{ exit $LastExitCode }

And then have the following in NLog.Targets.AtomicFile.Tests.csproj:

  <PropertyGroup Condition=" '$(TestTargetFramework)' != '' ">
    <OutputPath>.\bin\$(Configuration)\$(TestTargetFramework)\</OutputPath>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != '' ">
    <Reference Include="NLog.Targets.AtomicFile">
      <HintPath>../../src/NLog.Targets.AtomicFile/bin/$(Configuration)/$(TestTargetFramework)/NLog.Targets.AtomicFile.dll</HintPath>
      <Private>True</Private>
    </Reference>
  </ItemGroup>

This should make Net462 run with the NetStandard2.0 assembly-dll for doing atomic-file-writing using Win32-API.

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch 2 times, most recently from ec44894 to 2e22247 Compare February 28, 2026 03:52
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj`:
- Around line 37-46: The ProjectReference in the second ItemGroup must be
conditioned the same as the explicit Reference to avoid duplicate assembly
resolution: modify the ProjectReference (the ProjectReference element that
points to ..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj) so
it is only included when the explicit Reference is not used by adding a
Condition that mirrors the first ItemGroup (examine TargetFramework and
TestTargetFramework) to exclude the ProjectReference when TestTargetFramework is
set and TargetFramework != 'net8.0'.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec44894 and 2e22247.

📒 Files selected for processing (2)
  • run-tests.ps1
  • tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj

Comment on lines +37 to 46
<ItemGroup Condition=" '$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != '' ">
<Reference Include="NLog.Targets.AtomicFile">
<HintPath>../../src/NLog.Targets.AtomicFile/bin/$(Configuration)/$(TestTargetFramework)/NLog.Targets.AtomicFile.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj" />
</ItemGroup>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if similar patterns exist in other test projects in the repo for reference
echo "=== ProjectReference with Condition ==="
rg -n "ProjectReference.*Condition" --glob "*.csproj" -C2

echo -e "\n=== TestTargetFramework usage ==="
rg -n "TestTargetFramework" --glob "*.csproj" -C3

Repository: NLog/NLog

Length of output: 4443


🏁 Script executed:

cat -n tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj

Repository: NLog/NLog

Length of output: 2602


🏁 Script executed:

cat -n tests/NLog.UnitTests/NLog.UnitTests.csproj | head -80

Repository: NLog/NLog

Length of output: 4068


Add condition to ProjectReference to prevent duplicate assembly resolution.

When TestTargetFramework is set and TargetFramework is not net8.0, both the explicit Reference (lines 38-41) and the unconditional ProjectReference (line 45) will be active, causing MSBuild to resolve the same assembly from two different sources. This can lead to build warnings or unpredictable behavior.

Apply the condition to the ProjectReference to exclude it when the explicit Reference is used:

🔧 Proposed fix
-  <ItemGroup>
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' OR '$(TestTargetFramework)' == '' ">
     <ProjectReference Include="..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj" />
   </ItemGroup>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ItemGroup Condition=" '$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != '' ">
<Reference Include="NLog.Targets.AtomicFile">
<HintPath>../../src/NLog.Targets.AtomicFile/bin/$(Configuration)/$(TestTargetFramework)/NLog.Targets.AtomicFile.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != '' ">
<Reference Include="NLog.Targets.AtomicFile">
<HintPath>../../src/NLog.Targets.AtomicFile/bin/$(Configuration)/$(TestTargetFramework)/NLog.Targets.AtomicFile.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' OR '$(TestTargetFramework)' == '' ">
<ProjectReference Include="..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj" />
</ItemGroup>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj`
around lines 37 - 46, The ProjectReference in the second ItemGroup must be
conditioned the same as the explicit Reference to avoid duplicate assembly
resolution: modify the ProjectReference (the ProjectReference element that
points to ..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj) so
it is only included when the explicit Reference is not used by adding a
Condition that mirrors the first ItemGroup (examine TargetFramework and
TestTargetFramework) to exclude the ProjectReference when TestTargetFramework is
set and TargetFramework != 'net8.0'.

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch from 2e22247 to 57c1ca5 Compare February 28, 2026 04:06
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.

♻️ Duplicate comments (1)
tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj (1)

37-42: ⚠️ Potential issue | 🟠 Major

Prevent dual resolution of NLog.Targets.AtomicFile in this mode.

With this explicit <Reference> enabled, the unconditional <ProjectReference> at Line 45 can still be included, so MSBuild may resolve the same assembly from two paths. Gate the ProjectReference to the complementary condition.

🔧 Proposed fix
-  <ItemGroup>
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' OR '$(TestTargetFramework)' == '' ">
     <ProjectReference Include="..\..\src\NLog.Targets.AtomicFile\NLog.Targets.AtomicFile.csproj" />
   </ItemGroup>
#!/bin/bash
# Verify whether ProjectReference is still unconditional while explicit Reference is conditional.
python - <<'PY'
import xml.etree.ElementTree as ET
p = "tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj"
root = ET.parse(p).getroot()

def local(tag): return tag.split('}', 1)[-1]

project_ref_conditions = []
explicit_ref_conditions = []

for itemgroup in root:
    if local(itemgroup.tag) != "ItemGroup":
        continue
    cond = itemgroup.attrib.get("Condition", "").strip()
    for child in itemgroup:
        name = local(child.tag)
        include = child.attrib.get("Include", "")
        if name == "ProjectReference" and include.endswith("NLog.Targets.AtomicFile.csproj"):
            project_ref_conditions.append(cond)
        if name == "Reference" and include == "NLog.Targets.AtomicFile":
            explicit_ref_conditions.append(cond)

print("Explicit <Reference> conditions:", explicit_ref_conditions)
print("ProjectReference conditions:", project_ref_conditions)
print("Has unconditional ProjectReference:", any(c == "" for c in project_ref_conditions))
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj`
around lines 37 - 42, The explicit <Reference Include="NLog.Targets.AtomicFile">
is conditionally added when Condition=" '$(TargetFramework)' != 'net8.0' AND
'$(TestTargetFramework)' != '' ", but the corresponding <ProjectReference> for
NLog.Targets.AtomicFile remains unconditional and can cause dual resolution;
update the <ProjectReference Include="...NLog.Targets.AtomicFile.csproj"> block
so it is only present when the explicit Reference is not used (i.e., add the
complementary Condition to that ItemGroup or ProjectReference—e.g., the inverse
of "'$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != ''") to
ensure only one resolution path is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj`:
- Around line 37-42: The explicit <Reference Include="NLog.Targets.AtomicFile">
is conditionally added when Condition=" '$(TargetFramework)' != 'net8.0' AND
'$(TestTargetFramework)' != '' ", but the corresponding <ProjectReference> for
NLog.Targets.AtomicFile remains unconditional and can cause dual resolution;
update the <ProjectReference Include="...NLog.Targets.AtomicFile.csproj"> block
so it is only present when the explicit Reference is not used (i.e., add the
complementary Condition to that ItemGroup or ProjectReference—e.g., the inverse
of "'$(TargetFramework)' != 'net8.0' AND '$(TestTargetFramework)' != ''") to
ensure only one resolution path is active.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57c1ca5 and 709aecf.

📒 Files selected for processing (1)
  • tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj

@snakefoot
Copy link
Contributor

snakefoot commented Feb 28, 2026

Have been looking at how dotnet-runtime repo is using CreateFile, and forked your pull-request with additional changes:

  • Renamed the file Win32FileNativeMethods.cs to NativeMethods.cs to align with class-name
  • Renamed NativeMethods enum-values to match the Win32 defines (Easier to search afterwards)
  • Removed Win32FileAttributes since not needed.

If you can apply the changes to this pull-request, then I will merge and get release out this weekend:

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch from e1b6d81 to 47acbc7 Compare February 28, 2026 11:57
@michailf
Copy link
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

✅ Actions performed

Reviews resumed.

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 (2)
src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs (1)

82-84: Enum values are GENERIC_ constants, not FILE_GENERIC_*.*

The values 0x80000000, 0x40000000, and 0x20000000 correspond to GENERIC_READ, GENERIC_WRITE, and GENERIC_EXECUTE respectively. The actual FILE_GENERIC_* constants are composite values combining multiple rights. While these members aren't used in the current implementation, the naming could cause confusion when searching Win32 documentation.

This aligns with the reviewer feedback in PR comments suggesting renaming to match Win32 defines for easier searching.

♻️ Suggested rename to match Win32 defines
-            FILE_GENERIC_READ = 0x80000000,
-            FILE_GENERIC_WRITE = 0x40000000,
-            FILE_GENERIC_EXECUTE = 0x20000000,
+            GENERIC_READ = 0x80000000,
+            GENERIC_WRITE = 0x40000000,
+            GENERIC_EXECUTE = 0x20000000,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs` around lines 82 - 84,
Rename the enum members FILE_GENERIC_READ, FILE_GENERIC_WRITE, and
FILE_GENERIC_EXECUTE in NativeMethods.cs to GENERIC_READ, GENERIC_WRITE, and
GENERIC_EXECUTE respectively, since their hex values (0x80000000, 0x40000000,
0x20000000) map to the Win32 GENERIC_* constants; update any references (e.g.,
usages in methods within the same file or other files) to the new member names
to keep semantics identical and make the names searchable against Win32 docs.
src/NLog.Targets.AtomicFile/AtomicFileTarget.cs (1)

184-189: Consider using Dispose() or a using statement for cleaner resource management.

The current cleanup logic works, but the pattern could be simplified. Since SafeFileHandle implements IDisposable, calling Dispose() is more idiomatic than checking IsClosed and calling Close().

♻️ Simplified cleanup using Dispose
             catch
             {
-                if ((handle != null) && (!handle.IsClosed))
-                    handle.Close();
+                handle?.Dispose();
                 throw;
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs` around lines 184 - 189,
Replace the manual SafeFileHandle cleanup in the catch block with Dispose() (or
use a using statement where the SafeFileHandle is created) to rely on
IDisposable instead of checking IsClosed and calling Close(); specifically
update the catch that references the local variable handle (SafeFileHandle) in
AtomicFileTarget.cs so it calls handle.Dispose() (or refactor the handle
acquisition into a using block) before rethrowing to simplify and make resource
management idiomatic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs`:
- Around line 144-147: The bitwise check uses FileMode as if it were a flags
enum and can yield false positives; change the condition to a proper equality
check against the specific mode: replace "if ((fileMode & FileMode.Append) !=
0)" with a direct comparison (e.g., "if (fileMode == FileMode.Append)") so that
only the FileMode.Append case sets dwDesiredAccess |=
NativeMethods.Win32FileAccess.FILE_APPEND_DATA; reference the fileMode variable,
the condition, dwDesiredAccess and
NativeMethods.Win32FileAccess.FILE_APPEND_DATA in your change.

---

Nitpick comments:
In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs`:
- Around line 184-189: Replace the manual SafeFileHandle cleanup in the catch
block with Dispose() (or use a using statement where the SafeFileHandle is
created) to rely on IDisposable instead of checking IsClosed and calling
Close(); specifically update the catch that references the local variable handle
(SafeFileHandle) in AtomicFileTarget.cs so it calls handle.Dispose() (or
refactor the handle acquisition into a using block) before rethrowing to
simplify and make resource management idiomatic.

In `@src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs`:
- Around line 82-84: Rename the enum members FILE_GENERIC_READ,
FILE_GENERIC_WRITE, and FILE_GENERIC_EXECUTE in NativeMethods.cs to
GENERIC_READ, GENERIC_WRITE, and GENERIC_EXECUTE respectively, since their hex
values (0x80000000, 0x40000000, 0x20000000) map to the Win32 GENERIC_*
constants; update any references (e.g., usages in methods within the same file
or other files) to the new member names to keep semantics identical and make the
names searchable against Win32 docs.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 709aecf and 47acbc7.

📒 Files selected for processing (4)
  • run-tests.ps1
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs
  • tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj
🚧 Files skipped from review as they are similar to previous changes (2)
  • run-tests.ps1
  • tests/NLog.Targets.AtomicFile.Tests/NLog.Targets.AtomicFile.Tests.csproj

@michailf
Copy link
Contributor Author

Have been looking at how dotnet-runtime repo is using CreateFile, and forked your pull-request with additional changes:

  • Renamed the file Win32FileNativeMethods.cs to NativeMethods.cs to align with class-name
  • Renamed NativeMethods enum-values to match the Win32 defines (Easier to search afterwards)
  • Removed Win32FileAttributes since not needed.

If you can apply the changes to this pull-request, then I will merge and get release out this weekend:

I cherry-picked your changes.

One thing to note is that the fileMode is not truly used. It only understands how to parse FileMode.Append and we only pass FileMode.Append to the function.

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.

🧹 Nitpick comments (1)
src/NLog.Targets.AtomicFile/AtomicFileTarget.cs (1)

175-177: Minor style inconsistency with brace placement.

Line 175 uses K&R style (opening brace on same line), while the rest of the file uses Allman style (opening brace on new line). Consider aligning for consistency.

♻️ Suggested style fix
-                if (handle.IsInvalid) {
+                if (handle.IsInvalid)
+                {
                     System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(System.Runtime.InteropServices.Marshal.GetHRForLastWin32Error());
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs` around lines 175 - 177, The
if statement checking handle.IsInvalid uses K&R brace placement; change it to
Allman style to match the file by placing the opening brace on the next line so
the block for the condition "if (handle.IsInvalid)" matches the rest of the
file's brace convention (the block that calls
System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(...)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs`:
- Around line 175-177: The if statement checking handle.IsInvalid uses K&R brace
placement; change it to Allman style to match the file by placing the opening
brace on the next line so the block for the condition "if (handle.IsInvalid)"
matches the rest of the file's brace convention (the block that calls
System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(...)).

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47acbc7 and 907d1e3.

📒 Files selected for processing (1)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch 2 times, most recently from 0653a7c to 0625798 Compare February 28, 2026 12: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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs`:
- Around line 72-75: The XML doc comment for the constant FILE_APPEND_DATA is
incomplete; update the summary on FILE_APPEND_DATA in NativeMethods.cs so it
reads as a complete, clear sentence (for example: "For a file, the right to
append data to the file without overwriting existing data.") and ensure
punctuation and grammar are corrected so the docstring fully explains the
permission's intent.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 907d1e3 and 0625798.

📒 Files selected for processing (2)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs

@michailf michailf force-pushed the netstandard2.0_for_AtomicFile-tests branch from 0625798 to 2c4c8a3 Compare February 28, 2026 12:45
@sonarqubecloud
Copy link

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.

🧹 Nitpick comments (2)
src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs (1)

87-96: Consider documenting the FileMode parameter constraint.

The P/Invoke accepts FileMode directly for dwCreationDisposition. While .NET FileMode values 1-5 align with Win32 creation disposition constants, FileMode.Append (6) is invalid for Win32 and must be converted by callers. A comment clarifying this constraint would improve maintainability.

📝 Suggested documentation
+        /// <summary>
+        /// Creates or opens a file or I/O device.
+        /// </summary>
+        /// <remarks>
+        /// Note: FileMode.Append is not a valid Win32 creation disposition.
+        /// Callers must convert Append to OpenOrCreate and use FILE_APPEND_DATA access rights.
+        /// </remarks>
         [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
         [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
         public static extern Microsoft.Win32.SafeHandles.SafeFileHandle CreateFile(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs` around lines 87 - 96,
The CreateFile P/Invoke signature currently accepts a FileMode for the
dwCreationDisposition parameter; add a brief XML comment above the CreateFile
method explaining that .NET FileMode values 1–5 map to Win32 creation
dispositions but FileMode.Append (value 6) is not a valid Win32 disposition and
must be converted by callers (e.g., treat Append as OpenOrCreate with an
explicit file pointer move) before calling CreateFile to avoid invalid behavior.
Reference CreateFile and the FileMode dwCreationDisposition parameter in the
comment so maintainers know the constraint and conversion responsibility.
src/NLog.Targets.AtomicFile/AtomicFileTarget.cs (1)

136-157: Method is specialized for FileMode.Append despite accepting generic FileMode parameter.

The method accepts FileMode fileMode but only adds write access (FILE_APPEND_DATA) when fileMode == FileMode.Append. Other file modes would result in a handle without write permissions. Since this is currently only called with FileMode.Append, this works correctly, but the signature is misleading.

Consider either:

  1. Removing the fileMode parameter and hardcoding FileMode.Append behavior, or
  2. Adding validation/support for other modes if future callers might use them.
♻️ Option 1: Simplify to Append-only
-        private static FileStream CreateWindowsStream(string filePath, FileMode fileMode, FileShare fileShare, FileOptions fileOptions)
+        private static FileStream CreateWindowsStream(string filePath, FileShare fileShare, FileOptions fileOptions)
         {
             Microsoft.Win32.SafeHandles.SafeFileHandle? handle = null;
             try
             {

-                NativeMethods.Win32FileAccess dwDesiredAccess = NativeMethods.Win32FileAccess.SYNCHRONIZE;
-                if (fileMode == FileMode.Append)
-                {
-                    dwDesiredAccess |= NativeMethods.Win32FileAccess.FILE_APPEND_DATA;
-                }
+                NativeMethods.Win32FileAccess dwDesiredAccess = NativeMethods.Win32FileAccess.SYNCHRONIZE | NativeMethods.Win32FileAccess.FILE_APPEND_DATA;
                 if ((fileOptions & FileOptions.DeleteOnClose) != 0)
                 {
                     dwDesiredAccess |= NativeMethods.Win32FileAccess.DELETE; // required by FILE_DELETE_ON_CLOSE
                 }

-                // Must use a valid Win32 constant here...
-                if (fileMode == FileMode.Append)
-                {
-                    fileMode = FileMode.OpenOrCreate;
-                }
+                FileMode fileMode = FileMode.OpenOrCreate;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs` around lines 136 - 157, The
CreateWindowsStream method currently accepts a FileMode parameter but only
implements append semantics (adds FILE_APPEND_DATA and forces
FileMode.OpenOrCreate), which is misleading; change CreateWindowsStream to be
append-only by removing the FileMode parameter and hardcoding append behavior:
update the method signature (remove the fileMode argument), always include
NativeMethods.Win32FileAccess.FILE_APPEND_DATA in dwDesiredAccess, set the
effective fileMode to FileMode.OpenOrCreate inside the method, remove the
conditional branch that mutates fileMode, and update all callers to invoke
CreateWindowsStream without a FileMode argument; alternatively, if you want to
support other modes, add validation and branch logic to grant appropriate access
bits for FileMode.Create, FileMode.Truncate, FileMode.Open, etc., but prefer the
simpler append-only refactor given current usage in CreateWindowsStream.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/NLog.Targets.AtomicFile/AtomicFileTarget.cs`:
- Around line 136-157: The CreateWindowsStream method currently accepts a
FileMode parameter but only implements append semantics (adds FILE_APPEND_DATA
and forces FileMode.OpenOrCreate), which is misleading; change
CreateWindowsStream to be append-only by removing the FileMode parameter and
hardcoding append behavior: update the method signature (remove the fileMode
argument), always include NativeMethods.Win32FileAccess.FILE_APPEND_DATA in
dwDesiredAccess, set the effective fileMode to FileMode.OpenOrCreate inside the
method, remove the conditional branch that mutates fileMode, and update all
callers to invoke CreateWindowsStream without a FileMode argument;
alternatively, if you want to support other modes, add validation and branch
logic to grant appropriate access bits for FileMode.Create, FileMode.Truncate,
FileMode.Open, etc., but prefer the simpler append-only refactor given current
usage in CreateWindowsStream.

In `@src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs`:
- Around line 87-96: The CreateFile P/Invoke signature currently accepts a
FileMode for the dwCreationDisposition parameter; add a brief XML comment above
the CreateFile method explaining that .NET FileMode values 1–5 map to Win32
creation dispositions but FileMode.Append (value 6) is not a valid Win32
disposition and must be converted by callers (e.g., treat Append as OpenOrCreate
with an explicit file pointer move) before calling CreateFile to avoid invalid
behavior. Reference CreateFile and the FileMode dwCreationDisposition parameter
in the comment so maintainers know the constraint and conversion responsibility.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0625798 and 2c4c8a3.

📒 Files selected for processing (2)
  • src/NLog.Targets.AtomicFile/AtomicFileTarget.cs
  • src/NLog.Targets.AtomicFile/Internal/NativeMethods.cs

@sonarqubecloud
Copy link

@snakefoot
Copy link
Contributor

Thank you for the quick adjustments. Will merge and create new NLog release.

@snakefoot snakefoot merged commit 98d231d into NLog:dev Feb 28, 2026
6 of 7 checks passed
@welcome
Copy link

welcome bot commented Feb 28, 2026

Hooray your first pull request is merged! Thanks for the contribution! Looking for more? 👼 Please check the up-for-grabs issues - thanks!
thanks!

@snakefoot snakefoot linked an issue Feb 28, 2026 that may be closed by this pull request
@snakefoot snakefoot changed the title Netstandard2.0 for atomic file tests NLog.Targets.AtomicFile supporting NetStandard2 with Win32 API Feb 28, 2026
@snakefoot snakefoot added this to the 6.1.1 milestone Feb 28, 2026
@snakefoot snakefoot added the enhancement Improvement on existing feature label Feb 28, 2026
@snakefoot
Copy link
Contributor

snakefoot commented Feb 28, 2026

NLog.Targets.AtomicFile ver. 6.1.1 has been released:

Hope the new target platform netstandard2.0 gives you what you need.

Thank you for driving this, and making the effort to get this implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement on existing feature size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add .NET Standard 2.0 support to AtomicFile package

2 participants