diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index 866f807..4f9786d 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -72,7 +72,7 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL path, FileMode.Append, FileSystemRights.AppendData, - FileShare.ReadWrite, + FileShare.ReadWrite | FileShare.Delete, _fileStreamBufferLength, FileOptions.None); @@ -101,7 +101,23 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent) _path, FileMode.Append, FileSystemRights.AppendData, - FileShare.ReadWrite, + FileShare.ReadWrite | FileShare.Delete, + length, + FileOptions.None); + _fileStreamBufferLength = length; + + oldOutput.Dispose(); + } + + if (!System.IO.File.Exists(_path)) + { + var oldOutput = _fileOutput; + + _fileOutput = new FileStream( + _path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite | FileShare.Delete, length, FileOptions.None); _fileStreamBufferLength = length; diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs index 41a19ef..031bbcf 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs @@ -30,8 +30,9 @@ namespace Serilog.Sinks.File [Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(shared: true)` instead.")] public sealed class SharedFileSink : IFileSink, IDisposable { - readonly TextWriter _output; - readonly FileStream _underlyingStream; + TextWriter _output; + FileStream _underlyingStream; + readonly string _path; readonly ITextFormatter _textFormatter; readonly long? _fileSizeLimitBytes; readonly object _syncRoot = new object(); @@ -52,21 +53,21 @@ public sealed class SharedFileSink : IFileSink, IDisposable /// public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null) { - if (path == null) throw new ArgumentNullException(nameof(path)); + _path = path ?? throw new ArgumentNullException(nameof(path)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; - var directory = Path.GetDirectoryName(path); + var directory = Path.GetDirectoryName(_path); if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } - var mutexName = Path.GetFullPath(path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix; + var mutexName = Path.GetFullPath(_path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix; _mutex = new Mutex(false, mutexName); - _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + _underlyingStream = System.IO.File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); _output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } @@ -81,6 +82,13 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent) try { + if (!System.IO.File.Exists(_path)) + { + _underlyingStream.Dispose(); + _underlyingStream = System.IO.File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); + _output = new StreamWriter(_underlyingStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + _underlyingStream.Seek(0, SeekOrigin.End); if (_fileSizeLimitBytes != null) { diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index 10fe926..7c9698d 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -30,6 +30,38 @@ public void FileIsWrittenIfNonexistent() Assert.Contains("Hello, world!", lines[0]); } } + [Fact] + public void FileIsReWrittenAfterEventIfDeleted() + { + using (var tmp = TempFolder.ForCaller()) + { + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); + + void Emmit() + { + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); + } + } + + Emmit(); + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); + Assert.Single(lines); + + System.IO.File.Delete(nonexistent); + Assert.False(System.IO.File.Exists(nonexistent)); + Assert.Throws(() => System.IO.File.ReadAllLines(nonexistent)); + + Emmit(); + lines = System.IO.File.ReadAllLines(nonexistent); + Assert.True(System.IO.File.Exists(nonexistent)); + Assert.Contains("Hello, world!", lines[0]); + Assert.Single(lines); + } + } [Fact] public void FileIsAppendedToWhenAlreadyCreated() diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index d295dfc..e8a446f 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -4,6 +4,7 @@ using System.IO.Compression; using System.Linq; using System.Reflection; +using System.Threading; using Xunit; using Serilog.Events; using Serilog.Sinks.File.Tests.Support; @@ -261,6 +262,73 @@ public void IfTheLogFolderDoesNotExistItWillBeCreated() } } + [Fact] + public void ShouldReCreateDeletedFiles() + { + var fileName = Some.String() + "-{Date}.txt"; + var temp = Some.TempFolderPath(); + var folder = Path.Combine(temp, Guid.NewGuid().ToString()); + var pathFormat = Path.Combine(folder, fileName); + + Logger log = null; + + try + { + log = new LoggerConfiguration() + .WriteTo.File(pathFormat, rollingInterval: RollingInterval.Day, shared:true) + .CreateLogger(); + + log.Write(Some.InformationEvent()); + Assert.True(Directory.Exists(folder)); + + var createdFile = Directory.GetFiles(folder)[0]; + System.IO.File.Delete(createdFile); + Assert.False(System.IO.File.Exists(createdFile)); + + log = new LoggerConfiguration() + .WriteTo.File(pathFormat, rollingInterval: RollingInterval.Day, shared: true) + .CreateLogger(); + + log.Write(Some.InformationEvent()); + Assert.True(System.IO.File.Exists(createdFile)); + } + finally + { + log?.Dispose(); + Directory.Delete(temp, true); + } + } + + [Fact] + public void ShouldBeAbleToDeleteFile() + { + var fileName = Some.String() + "-{Date}.txt"; + var temp = Some.TempFolderPath(); + var folder = Path.Combine(temp, Guid.NewGuid().ToString()); + var pathFormat = Path.Combine(folder, fileName); + + Logger log = null; + + try + { + log = new LoggerConfiguration() + .WriteTo.File(pathFormat, retainedFileCountLimit: 3, rollingInterval: RollingInterval.Day, shared:true, flushToDiskInterval:TimeSpan.FromSeconds(1), buffered:false) + .CreateLogger(); + + log.Write(Some.InformationEvent()); + Assert.True(Directory.Exists(folder)); + + var createdFile = Directory.GetFiles(folder)[0]; + System.IO.File.Delete(createdFile); + Assert.False(System.IO.File.Exists(createdFile)); + } + finally + { + log?.Dispose(); + Directory.Delete(temp, true); + } + } + [Fact] public void AssemblyVersionIsFixedAt200() { diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index 565be9b..5832825 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -26,6 +26,38 @@ public void FileIsWrittenIfNonexistent() Assert.Contains("Hello, world!", lines[0]); } } + [Fact] + public void FileIsReWrittenAfterEventIfDeleted() + { + using (var tmp = TempFolder.ForCaller()) + { + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); + + void Emmit() + { + using (var sink = new SharedFileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); + } + } + + Emmit(); + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); + Assert.Single(lines); + + System.IO.File.Delete(nonexistent); + Assert.False(System.IO.File.Exists(nonexistent)); + Assert.Throws(() => System.IO.File.ReadAllLines(nonexistent)); + + Emmit(); + lines = System.IO.File.ReadAllLines(nonexistent); + Assert.True(System.IO.File.Exists(nonexistent)); + Assert.Contains("Hello, world!", lines[0]); + Assert.Single(lines); + } + } [Fact] public void FileIsAppendedToWhenAlreadyCreated()