From bb2266375701fe036026f58a644a889e4988fecd Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 13:12:18 +0000 Subject: [PATCH 01/13] updated to use FileSystemAclExtensions.Create() to append to file --- .../Serilog.Sinks.File.csproj | 4 +- .../Sinks/File/SharedFileSink.AtomicAppend.cs | 49 +++++++++++++++++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 2b744c1..bbc32cb 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -34,11 +34,11 @@ - $(DefineConstants);ENUMERABLE_MAXBY + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND - $(DefineConstants);ENUMERABLE_MAXBY + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND 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 From 6cf1330f78d543cfa32585718ac16eb107e66cc1 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 13:26:43 +0000 Subject: [PATCH 02/13] added net7 as well --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index bbc32cb..7d3fe18 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -33,6 +33,10 @@ $(DefineConstants);ENUMERABLE_MAXBY + + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND + + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND From ccd907bec40bd6309403a9dd259bc2867a331a41 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 13:35:36 +0000 Subject: [PATCH 03/13] added to list --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 7d3fe18..289f3af 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -8,7 +8,7 @@ - $(TargetFrameworks);net9.0;net8.0;net6.0;netstandard2.0 + $(TargetFrameworks);net9.0;net8.0;net7.0;net6.0;netstandard2.0 serilog;file https://github.com/serilog/serilog-sinks-file Apache-2.0 From af13b14c87ca0bb434b3cc1796a83783389c1c58 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 13:40:24 +0000 Subject: [PATCH 04/13] removed net7 for now --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 289f3af..bbc32cb 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -8,7 +8,7 @@ - $(TargetFrameworks);net9.0;net8.0;net7.0;net6.0;netstandard2.0 + $(TargetFrameworks);net9.0;net8.0;net6.0;netstandard2.0 serilog;file https://github.com/serilog/serilog-sinks-file Apache-2.0 @@ -33,10 +33,6 @@ $(DefineConstants);ENUMERABLE_MAXBY - - $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND - - $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND From 3245d2fb78eec4b20cd68648e199d1bf30dcba71 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 16:32:16 +0000 Subject: [PATCH 05/13] no need for checks --- .../Sinks/File/SharedFileSink.AtomicAppend.cs | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index f7abd87..0d6344e 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -80,20 +80,14 @@ 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. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - _fileOutput = CreateFile( - path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - _fileStreamBufferLength, - FileOptions.None); - } - else - { - throw new NotSupportedException(); - } + _fileOutput = CreateFile( + path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite, + _fileStreamBufferLength, + FileOptions.None); + _writeBuffer = new MemoryStream(); _output = new StreamWriter(_writeBuffer, @@ -115,21 +109,15 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent) if (length > _fileStreamBufferLength) { var oldOutput = _fileOutput; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - _fileOutput = CreateFile( - _path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - length, - FileOptions.None); + + _fileOutput = CreateFile( + _path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite, + length, + FileOptions.None); _fileStreamBufferLength = length; - } - else - { - throw new NotSupportedException(); - } oldOutput.Dispose(); } From 93f873fb9c66bb5f0b122e82d938f96337990c9c Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 16:32:42 +0000 Subject: [PATCH 06/13] ensuring net6 uses os_mutex --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index bbc32cb..0c5c927 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -25,7 +25,7 @@ $(DefineConstants);ATOMIC_APPEND;HRESULTS - + $(DefineConstants);OS_MUTEX @@ -46,4 +46,4 @@ - + \ No newline at end of file From 4627ad3f03e5179e7dc61b67e0bd1e1aebcd916c Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Thu, 20 Mar 2025 17:03:52 +0000 Subject: [PATCH 07/13] checking for platform required for .net8 to work --- .../Sinks/File/SharedFileSink.AtomicAppend.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index 0d6344e..f7abd87 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -80,14 +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 = CreateFile( - path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - _fileStreamBufferLength, - FileOptions.None); - + 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, @@ -109,15 +115,21 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent) if (length > _fileStreamBufferLength) { var oldOutput = _fileOutput; - - _fileOutput = CreateFile( - _path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - length, - FileOptions.None); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _fileOutput = CreateFile( + _path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite, + length, + FileOptions.None); _fileStreamBufferLength = length; + } + else + { + throw new NotSupportedException(); + } oldOutput.Dispose(); } From 7691dcb5cad5edee6c4282dab5817f8521b7b19e Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Mon, 24 Mar 2025 09:48:26 +0000 Subject: [PATCH 08/13] creating new branch as we are only interested on .net8 --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 0c5c927..c62cc8b 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -33,7 +33,7 @@ $(DefineConstants);ENUMERABLE_MAXBY - + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND From 89bc34fdc99a1e542913a184d53590e0c12d5c01 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Mon, 24 Mar 2025 09:48:33 +0000 Subject: [PATCH 09/13] removed whitespace --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index c62cc8b..0c5c927 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -33,7 +33,7 @@ $(DefineConstants);ENUMERABLE_MAXBY - + $(DefineConstants);ENUMERABLE_MAXBY;ATOMIC_APPEND From 6822f7958c9b6eabee3a9361f70e078dd98c6173 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Mon, 24 Mar 2025 15:46:48 +0000 Subject: [PATCH 10/13] added tests for atomic operations --- .../SharedFileSinkTests.cs | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index b784d2f..a03e4e8 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,61 @@ 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 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."); + } } From 7141e6d2067888310568941fd70348dd906225d2 Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Mon, 24 Mar 2025 15:53:07 +0000 Subject: [PATCH 11/13] added tests for multiple users accessing file sink writes to --- .../SharedFileSinkTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index a03e4e8..b6e1e39 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -123,6 +123,45 @@ public void FileIsNotLockedAfterDisposal() 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() { From c13bb1acbf41cdddcd246b6435982b457650ae8d Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Wed, 26 Mar 2025 09:16:45 +0000 Subject: [PATCH 12/13] simplified statements --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 0c5c927..91b1a77 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -25,12 +25,12 @@ $(DefineConstants);ATOMIC_APPEND;HRESULTS - + $(DefineConstants);OS_MUTEX - $(DefineConstants);ENUMERABLE_MAXBY + $(DefineConstants);ENUMERABLE_MAXBY;OS_MUTEX From 8e7ba53220b88fe8db801e173d07a6b95e95d53b Mon Sep 17 00:00:00 2001 From: cortex-bt Date: Wed, 26 Mar 2025 10:50:46 +0000 Subject: [PATCH 13/13] shoulde be a == instead of != --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 91b1a77..9c27ef9 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -25,7 +25,7 @@ $(DefineConstants);ATOMIC_APPEND;HRESULTS - + $(DefineConstants);OS_MUTEX