Skip to content

Allow FileLifecycleHooks to change the length of the stream #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]);
}
}
}
}