Skip to content

Commit 7e65244

Browse files
Allow FileLifecycleHooks to change the length of the stream. Resolves serilog#186
1 parent 7cf84eb commit 7e65244

File tree

5 files changed

+137
-2
lines changed

5 files changed

+137
-2
lines changed

src/Serilog.Sinks.File/Sinks/File/FileSink.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ internal FileSink(
8080
Directory.CreateDirectory(directory);
8181
}
8282

83-
Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
83+
Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
84+
outputStream.Seek(0, SeekOrigin.End);
85+
8486
if (_fileSizeLimitBytes != null)
8587
{
8688
outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream);

src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ public override long Seek(long offset, SeekOrigin origin)
6363

6464
public override void SetLength(long value)
6565
{
66-
throw new NotSupportedException();
66+
_stream.SetLength(value);
67+
68+
if (value < CountedLength)
69+
{
70+
// File is now shorter and our position has changed to _stream.Length
71+
CountedLength = _stream.Length;
72+
}
6773
}
6874

6975
public override int Read(byte[] buffer, int offset, int count)

test/Serilog.Sinks.File.Tests/FileSinkTests.cs

+26
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,32 @@ public static void OnOpenedLifecycleHookCanCaptureFilePath()
223223
}
224224
}
225225

226+
[Fact]
227+
public static void OnOpenedLifecycleHookCanEmptyTheFileContents()
228+
{
229+
using (var tmp = TempFolder.ForCaller())
230+
{
231+
var emptyFileHook = new TruncateFileHook();
232+
233+
var path = tmp.AllocateFilename("txt");
234+
using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false))
235+
{
236+
sink.Emit(Some.LogEvent());
237+
}
238+
239+
using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook))
240+
{
241+
// Hook will clear the contents of the file before emitting the log events
242+
sink.Emit(Some.LogEvent());
243+
}
244+
245+
var lines = System.IO.File.ReadAllLines(path);
246+
247+
Assert.Single(lines);
248+
Assert.Equal('{', lines[0][0]);
249+
}
250+
}
251+
226252
static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding)
227253
{
228254
using (var tmp = TempFolder.ForCaller())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.IO;
2+
using System.Text;
3+
4+
namespace Serilog.Sinks.File.Tests.Support
5+
{
6+
/// <inheritdoc />
7+
/// <summary>
8+
/// Demonstrates the use of <seealso cref="T:Serilog.FileLifecycleHooks" />, by emptying the file before it's written to
9+
/// </summary>
10+
public class TruncateFileHook : FileLifecycleHooks
11+
{
12+
public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding)
13+
{
14+
underlyingStream.SetLength(0);
15+
return base.OnFileOpened(underlyingStream, encoding);
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.IO;
2+
using System.Text;
3+
using Serilog.Sinks.File.Tests.Support;
4+
using Xunit;
5+
6+
namespace Serilog.Sinks.File.Tests
7+
{
8+
public class WriteCountingStreamTests
9+
{
10+
[Fact]
11+
public void CountedLengthIsResetToStreamLengthIfNewSizeIsSmaller()
12+
{
13+
// If we counted 10 bytes written and SetLength was called with a smaller length (e.g. 5)
14+
// we adjust the counter to the new byte count of the file to reflect reality
15+
16+
using (var tmp = TempFolder.ForCaller())
17+
{
18+
var path = tmp.AllocateFilename("txt");
19+
20+
long streamLengthAfterSetLength;
21+
long countedLengthAfterSetLength;
22+
23+
using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read))
24+
using (var countingStream = new WriteCountingStream(fileStream))
25+
using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false)))
26+
{
27+
writer.WriteLine("Hello, world!");
28+
writer.Flush();
29+
30+
countingStream.SetLength(5);
31+
streamLengthAfterSetLength = countingStream.Length;
32+
countedLengthAfterSetLength = countingStream.CountedLength;
33+
}
34+
35+
Assert.Equal(5, streamLengthAfterSetLength);
36+
Assert.Equal(5, countedLengthAfterSetLength);
37+
38+
var lines = System.IO.File.ReadAllLines(path);
39+
40+
Assert.Single(lines);
41+
Assert.Equal("Hello", lines[0]);
42+
}
43+
}
44+
45+
[Fact]
46+
public void CountedLengthRemainsTheSameIfNewSizeIsLarger()
47+
{
48+
// If we counted 10 bytes written and SetLength was called with a larger length (e.g. 100)
49+
// we leave the counter intact because our position on the stream remains the same... The
50+
// file just grew larger in size
51+
52+
using (var tmp = TempFolder.ForCaller())
53+
{
54+
var path = tmp.AllocateFilename("txt");
55+
56+
long streamLengthBeforeSetLength;
57+
long streamLengthAfterSetLength;
58+
long countedLengthAfterSetLength;
59+
60+
using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read))
61+
using (var countingStream = new WriteCountingStream(fileStream))
62+
using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false)))
63+
{
64+
writer.WriteLine("Hello, world!");
65+
writer.Flush();
66+
67+
streamLengthBeforeSetLength = countingStream.CountedLength;
68+
countingStream.SetLength(100);
69+
streamLengthAfterSetLength = countingStream.Length;
70+
countedLengthAfterSetLength = countingStream.CountedLength;
71+
}
72+
73+
Assert.Equal(100, streamLengthAfterSetLength);
74+
Assert.Equal(streamLengthBeforeSetLength, countedLengthAfterSetLength);
75+
76+
var lines = System.IO.File.ReadAllLines(path);
77+
78+
Assert.Equal(2, lines.Length);
79+
Assert.Equal("Hello, world!", lines[0]);
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)