diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj
index 2b744c1..9c27ef9 100644
--- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj
+++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj
@@ -25,20 +25,20 @@
$(DefineConstants);ATOMIC_APPEND;HRESULTS
-
+
$(DefineConstants);OS_MUTEX
- $(DefineConstants);ENUMERABLE_MAXBY
+ $(DefineConstants);ENUMERABLE_MAXBY;OS_MUTEX
- $(DefineConstants);ENUMERABLE_MAXBY
+ $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND
- $(DefineConstants);ENUMERABLE_MAXBY
+ $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND
@@ -46,4 +46,4 @@
-
+
\ No newline at end of file
diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
index 485c1e4..f7abd87 100644
--- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
@@ -1,4 +1,4 @@
-// Copyright 2013-2019 Serilog Contributors
+// Copyright 2013-2019 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
#if ATOMIC_APPEND
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Security.AccessControl;
using System.Text;
using Serilog.Core;
@@ -77,13 +80,20 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
// FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND, but that API is not yet
// exposed by .NET Core.
- _fileOutput = new FileStream(
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ _fileOutput = CreateFile(
path,
FileMode.Append,
FileSystemRights.AppendData,
FileShare.ReadWrite,
_fileStreamBufferLength,
FileOptions.None);
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
_writeBuffer = new MemoryStream();
_output = new StreamWriter(_writeBuffer,
@@ -105,8 +115,9 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
if (length > _fileStreamBufferLength)
{
var oldOutput = _fileOutput;
-
- _fileOutput = new FileStream(
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ _fileOutput = CreateFile(
_path,
FileMode.Append,
FileSystemRights.AppendData,
@@ -114,6 +125,11 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
length,
FileOptions.None);
_fileStreamBufferLength = length;
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
oldOutput.Dispose();
}
@@ -188,6 +204,31 @@ void ISetLoggingFailureListener.SetFailureListener(ILoggingFailureListener failu
{
_failureListener = failureListener ?? throw new ArgumentNullException(nameof(failureListener));
}
+
+ private static FileStream CreateFile(string path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options)
+ {
+ // FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND
+#if NET48
+ _fileOutput = new FileStream(path, mode, rights, share, bufferSize, options);
+#else
+ // In .NET 7 for Windows it's exposed with FileSystemAclExtensions.Create API
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var _fileOutput = FileSystemAclExtensions.Create(new FileInfo(path), mode, rights, share, bufferSize, options, new FileSecurity());
+
+ // Inherit ACL from container
+ var security = new FileSecurity();
+ security.SetAccessRuleProtection(false, false);
+ FileSystemAclExtensions.SetAccessControl(new FileInfo(path), security);
+
+ return _fileOutput;
+ }
+ else
+ {
+ throw new NotSupportedException();
+ }
+#endif
+ }
}
#endif
diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs
index b784d2f..b6e1e39 100644
--- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs
+++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs
@@ -1,4 +1,4 @@
-using Serilog.Core;
+using Serilog.Core;
using Xunit;
using Serilog.Formatting.Json;
using Serilog.Sinks.File.Tests.Support;
@@ -94,4 +94,100 @@ public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted()
var size = new FileInfo(path).Length;
Assert.True(size > maxBytes * 2);
}
+
+ [Fact]
+ public void FileIsNotLockedAfterDisposal()
+ {
+ using var tmp = TempFolder.ForCaller();
+ var path = tmp.AllocateFilename("txt");
+ var evt = Some.LogEvent("Hello, world!");
+
+ using (var sink = new SharedFileSink(path, new JsonFormatter(), null))
+ {
+ sink.Emit(evt);
+ }
+
+ // Ensure the file is not locked after the sink is disposed
+ var exceptionThrown = false;
+ try
+ {
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
+ {
+ }
+ }
+ catch (IOException)
+ {
+ exceptionThrown = true;
+ }
+
+ Assert.False(exceptionThrown, "File should not be locked after sink disposal.");
+ }
+
+ [Fact]
+ public void FileIsLockedByOneUserAndAnotherUserTriesToWrite()
+ {
+ using var tmp = TempFolder.ForCaller();
+ var path = tmp.AllocateFilename("txt");
+ var evt = Some.LogEvent("Hello, world!");
+
+ // Lock the file by one user
+ using (var stream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
+ {
+ using (var writer = new StreamWriter(stream))
+ {
+ writer.WriteLine("Initial content");
+ writer.Flush();
+
+ // Try to write to the locked file by another user
+ var exceptionThrown = false;
+ try
+ {
+ using (var sink = new SharedFileSink(path, new JsonFormatter(), null))
+ {
+ sink.Emit(evt);
+ }
+ }
+ catch (IOException)
+ {
+ exceptionThrown = true;
+ }
+
+ Assert.True(exceptionThrown, "IOException should be thrown when trying to write to a locked file.");
+ }
+ }
+
+ // Verify the file content
+ var lines = System.IO.File.ReadAllLines(path);
+ Assert.Contains("Initial content", lines);
+ Assert.DoesNotContain("Hello, world!", lines);
+ }
+
+ [Fact]
+ public async Task FileIsNotLockedDuringAsyncOperations()
+ {
+ using var tmp = TempFolder.ForCaller();
+ var path = tmp.AllocateFilename("txt");
+ var evt = Some.LogEvent("Hello, world!");
+
+ using (var sink = new SharedFileSink(path, new JsonFormatter(), null))
+ {
+ await Task.Run(() => sink.Emit(evt));
+ }
+
+ // Ensure the file is not locked after async operations
+ var exceptionThrown = false;
+ try
+ {
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite))
+ {
+ stream.ReadAllLines();
+ }
+ }
+ catch (IOException)
+ {
+ exceptionThrown = true;
+ }
+
+ Assert.False(exceptionThrown, "File should not be locked after async operations.");
+ }
}