Skip to content

Commit

Permalink
Allow FileLifecycleHooks to change the length of the stream. Resolves #…
Browse files Browse the repository at this point in the history
  • Loading branch information
augustoproiete authored and nblumhardt committed Jun 22, 2021
1 parent edd4ba9 commit a2fa447
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions test/Serilog.Sinks.File.Tests/FileSinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
18 changes: 18 additions & 0 deletions test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.IO;
using System.Text;

namespace Serilog.Sinks.File.Tests.Support
{
/// <inheritdoc />
/// <summary>
/// Demonstrates the use of <seealso cref="T:Serilog.FileLifecycleHooks" />, by emptying the file before it's written to
/// </summary>
public class TruncateFileHook : FileLifecycleHooks
{
public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding)
{
underlyingStream.SetLength(0);
return base.OnFileOpened(underlyingStream, encoding);
}
}
}
83 changes: 83 additions & 0 deletions test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs
Original file line number Diff line number Diff line change
@@ -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]);
}
}
}
}

0 comments on commit a2fa447

Please sign in to comment.