Skip to content

Commit 0d3a208

Browse files
authored
Merge pull request #356 from BernhardMarconato/dev
Always flush sink on Fatal events
2 parents fa24052 + dd0c32b commit 0d3a208

File tree

5 files changed

+61
-18
lines changed

5 files changed

+61
-18
lines changed

Directory.Version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionPrefix>7.0.1</VersionPrefix>
3+
<VersionPrefix>8.0.0</VersionPrefix>
44
</PropertyGroup>
55
</Project>

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ Only a limited subset of configuration options are currently available in this m
189189

190190
### Performance
191191

192-
By default, the file sink will flush each event written through it to disk. To improve write performance, specifying `buffered: true` will permit the underlying stream to buffer writes.
192+
By default, the file sink will flush each event written through it to disk. To improve write performance, specifying `buffered: true` will permit the underlying stream to buffer writes. However, events with `LogEventLevel.Fatal` will always be flushed to disk immediately.
193193

194194
The [Serilog.Sinks.Async](https://github.com/serilog/serilog-sinks-async) package can be used to wrap the file sink and perform all disk access on a background worker thread.
195195

src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public static class FileLoggerConfigurationExtensions
4949
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
5050
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
5151
/// will be written in full even if it exceeds the limit.</param>
52-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
53-
/// is false.</param>
52+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
53+
/// can be buffered or not. The default is false.</param>
5454
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
5555
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
5656
/// <returns>Configuration object allowing method chaining.</returns>
@@ -89,8 +89,8 @@ public static LoggerConfiguration File(
8989
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
9090
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
9191
/// will be written in full even if it exceeds the limit.</param>
92-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
93-
/// is false.</param>
92+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
93+
/// can be buffered or not. The default is false.</param>
9494
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
9595
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
9696
/// <returns>Configuration object allowing method chaining.</returns>
@@ -126,8 +126,8 @@ public static LoggerConfiguration File(
126126
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
127127
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
128128
/// will be written in full even if it exceeds the limit.</param>
129-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
130-
/// is false.</param>
129+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
130+
/// can be buffered or not. The default is false.</param>
131131
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
132132
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
133133
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
@@ -175,8 +175,8 @@ public static LoggerConfiguration File(
175175
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
176176
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
177177
/// will be written in full even if it exceeds the limit.</param>
178-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
179-
/// is false.</param>
178+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
179+
/// can be buffered or not. The default is false.</param>
180180
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
181181
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
182182
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
@@ -221,8 +221,8 @@ public static LoggerConfiguration File(
221221
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
222222
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
223223
/// will be written in full even if it exceeds the limit.</param>
224-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
225-
/// is false.</param>
224+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
225+
/// can be buffered or not. The default is false.</param>
226226
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
227227
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
228228
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
@@ -291,8 +291,8 @@ public static LoggerConfiguration File(
291291
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
292292
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
293293
/// will be written in full even if it exceeds the limit.</param>
294-
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
295-
/// is false.</param>
294+
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
295+
/// can be buffered or not. The default is false.</param>
296296
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
297297
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
298298
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
122122
}
123123

124124
_textFormatter.Format(logEvent, _output);
125-
if (!_buffered)
125+
126+
if (logEvent.Level == LogEventLevel.Fatal)
127+
FlushToDisk();
128+
else if (!_buffered)
126129
_output.Flush();
127130

128131
return true;

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

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using System.IO.Compression;
2-
using System.Text;
31
using Serilog.Core;
4-
using Xunit;
2+
using Serilog.Events;
53
using Serilog.Formatting.Json;
64
using Serilog.Sinks.File.Tests.Support;
75
using Serilog.Tests.Support;
6+
using System.IO.Compression;
7+
using System.Text;
8+
using Xunit;
89

910
#pragma warning disable 618
1011

@@ -235,6 +236,28 @@ public static void OnOpenedLifecycleHookCanEmptyTheFileContents()
235236
Assert.Equal('{', lines[0][0]);
236237
}
237238

239+
[Fact]
240+
public void WhenBufferedFatalEventFlushesAllPendingEvents()
241+
{
242+
using var tmp = TempFolder.ForCaller();
243+
var path = tmp.AllocateFilename("txt");
244+
var formatter = new JsonFormatter();
245+
246+
using (var sink = new FileSink(path, formatter, null, null, true))
247+
{
248+
sink.Emit(Some.LogEvent(level: LogEventLevel.Information));
249+
sink.Emit(Some.LogEvent(level: LogEventLevel.Warning));
250+
251+
var lines = ReadAllLinesShared(path);
252+
Assert.Empty(lines);
253+
254+
sink.Emit(Some.LogEvent(level: LogEventLevel.Fatal));
255+
256+
lines = ReadAllLinesShared(path);
257+
Assert.Equal(3, lines.Length);
258+
}
259+
}
260+
238261
static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding)
239262
{
240263
using var tmp = TempFolder.ForCaller();
@@ -260,4 +283,21 @@ static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding enco
260283
size = new FileInfo(path).Length;
261284
Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size);
262285
}
286+
287+
private static string[] ReadAllLinesShared(string path)
288+
{
289+
// ReadAllLines cannot be used here, as it can't read files even if they are opened with FileShare.Read
290+
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
291+
using var reader = new StreamReader(fs);
292+
293+
string? line;
294+
List<string> lines = [];
295+
296+
while ((line = reader.ReadLine()) != null)
297+
{
298+
lines.Add(line);
299+
}
300+
301+
return [.. lines];
302+
}
263303
}

0 commit comments

Comments
 (0)