diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index dd83a3f..b1e3cca 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -80,7 +80,9 @@ internal FileSink( Directory.CreateDirectory(directory); } - Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read); + Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); + outputStream.Seek(0, SeekOrigin.End); + if (_fileSizeLimitBytes != null) { outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); diff --git a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs index fe0d5d3..e247144 100644 --- a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs +++ b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs @@ -63,7 +63,13 @@ public override long Seek(long offset, SeekOrigin origin) public override void SetLength(long value) { - throw new NotSupportedException(); + _stream.SetLength(value); + + if (value < CountedLength) + { + // File is now shorter and our position has changed to _stream.Length + CountedLength = _stream.Length; + } } public override int Read(byte[] buffer, int offset, int count) diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index 80c088f..a33261f 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -223,6 +223,32 @@ public static void OnOpenedLifecycleHookCanCaptureFilePath() } } + [Fact] + public static void OnOpenedLifecycleHookCanEmptyTheFileContents() + { + using (var tmp = TempFolder.ForCaller()) + { + var emptyFileHook = new TruncateFileHook(); + + var path = tmp.AllocateFilename("txt"); + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false)) + { + sink.Emit(Some.LogEvent()); + } + + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook)) + { + // Hook will clear the contents of the file before emitting the log events + sink.Emit(Some.LogEvent()); + } + + var lines = System.IO.File.ReadAllLines(path); + + Assert.Single(lines); + Assert.Equal('{', lines[0][0]); + } + } + static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) { using (var tmp = TempFolder.ForCaller()) diff --git a/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs b/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs new file mode 100644 index 0000000..63f8497 --- /dev/null +++ b/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.Text; + +namespace Serilog.Sinks.File.Tests.Support +{ + /// + /// + /// Demonstrates the use of , by emptying the file before it's written to + /// + public class TruncateFileHook : FileLifecycleHooks + { + public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) + { + underlyingStream.SetLength(0); + return base.OnFileOpened(underlyingStream, encoding); + } + } +} diff --git a/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs b/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs new file mode 100644 index 0000000..887ffe2 --- /dev/null +++ b/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs @@ -0,0 +1,83 @@ +using System.IO; +using System.Text; +using Serilog.Sinks.File.Tests.Support; +using Xunit; + +namespace Serilog.Sinks.File.Tests +{ + public class WriteCountingStreamTests + { + [Fact] + public void CountedLengthIsResetToStreamLengthIfNewSizeIsSmaller() + { + // If we counted 10 bytes written and SetLength was called with a smaller length (e.g. 5) + // we adjust the counter to the new byte count of the file to reflect reality + + using (var tmp = TempFolder.ForCaller()) + { + var path = tmp.AllocateFilename("txt"); + + long streamLengthAfterSetLength; + long countedLengthAfterSetLength; + + using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var countingStream = new WriteCountingStream(fileStream)) + using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) + { + writer.WriteLine("Hello, world!"); + writer.Flush(); + + countingStream.SetLength(5); + streamLengthAfterSetLength = countingStream.Length; + countedLengthAfterSetLength = countingStream.CountedLength; + } + + Assert.Equal(5, streamLengthAfterSetLength); + Assert.Equal(5, countedLengthAfterSetLength); + + var lines = System.IO.File.ReadAllLines(path); + + Assert.Single(lines); + Assert.Equal("Hello", lines[0]); + } + } + + [Fact] + public void CountedLengthRemainsTheSameIfNewSizeIsLarger() + { + // If we counted 10 bytes written and SetLength was called with a larger length (e.g. 100) + // we leave the counter intact because our position on the stream remains the same... The + // file just grew larger in size + + using (var tmp = TempFolder.ForCaller()) + { + var path = tmp.AllocateFilename("txt"); + + long streamLengthBeforeSetLength; + long streamLengthAfterSetLength; + long countedLengthAfterSetLength; + + using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var countingStream = new WriteCountingStream(fileStream)) + using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) + { + writer.WriteLine("Hello, world!"); + writer.Flush(); + + streamLengthBeforeSetLength = countingStream.CountedLength; + countingStream.SetLength(100); + streamLengthAfterSetLength = countingStream.Length; + countedLengthAfterSetLength = countingStream.CountedLength; + } + + Assert.Equal(100, streamLengthAfterSetLength); + Assert.Equal(streamLengthBeforeSetLength, countedLengthAfterSetLength); + + var lines = System.IO.File.ReadAllLines(path); + + Assert.Equal(2, lines.Length); + Assert.Equal("Hello, world!", lines[0]); + } + } + } +}