diff --git a/Directory.Version.props b/Directory.Version.props
index 8596ae5..2e9cd58 100644
--- a/Directory.Version.props
+++ b/Directory.Version.props
@@ -1,5 +1,5 @@
- 7.0.1
+ 8.0.0
diff --git a/README.md b/README.md
index 1feed22..9eec1d2 100644
--- a/README.md
+++ b/README.md
@@ -189,7 +189,7 @@ Only a limited subset of configuration options are currently available in this m
### Performance
-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.
+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.
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.
diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
index e3e8bcf..d75124b 100644
--- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
+++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
@@ -49,8 +49,8 @@ public static class FileLoggerConfigurationExtensions
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// Configuration object allowing method chaining.
@@ -89,8 +89,8 @@ public static LoggerConfiguration File(
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// Configuration object allowing method chaining.
@@ -126,8 +126,8 @@ public static LoggerConfiguration File(
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// The interval at which logging will roll over to a new file.
@@ -175,8 +175,8 @@ public static LoggerConfiguration File(
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// The interval at which logging will roll over to a new file.
@@ -221,8 +221,8 @@ public static LoggerConfiguration File(
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// The interval at which logging will roll over to a new file.
@@ -291,8 +291,8 @@ public static LoggerConfiguration File(
/// The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.
- /// Indicates if flushing to the output file can be buffered or not. The default
- /// is false.
+ /// Indicates if flushing of non-fatal events to the output file
+ /// can be buffered or not. The default is false.
/// Allow the log file to be shared by multiple processes. The default is false.
/// If provided, a full disk flush will be performed periodically at the specified interval.
/// The interval at which logging will roll over to a new file.
diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
index 32c0cd3..14c9f56 100644
--- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
@@ -122,7 +122,10 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
}
_textFormatter.Format(logEvent, _output);
- if (!_buffered)
+
+ if (logEvent.Level == LogEventLevel.Fatal)
+ FlushToDisk();
+ else if (!_buffered)
_output.Flush();
return true;
diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs
index b42a562..fa7ea49 100644
--- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs
+++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs
@@ -1,10 +1,11 @@
-using System.IO.Compression;
-using System.Text;
using Serilog.Core;
-using Xunit;
+using Serilog.Events;
using Serilog.Formatting.Json;
using Serilog.Sinks.File.Tests.Support;
using Serilog.Tests.Support;
+using System.IO.Compression;
+using System.Text;
+using Xunit;
#pragma warning disable 618
@@ -235,6 +236,28 @@ public static void OnOpenedLifecycleHookCanEmptyTheFileContents()
Assert.Equal('{', lines[0][0]);
}
+ [Fact]
+ public void WhenBufferedFatalEventFlushesAllPendingEvents()
+ {
+ using var tmp = TempFolder.ForCaller();
+ var path = tmp.AllocateFilename("txt");
+ var formatter = new JsonFormatter();
+
+ using (var sink = new FileSink(path, formatter, null, null, true))
+ {
+ sink.Emit(Some.LogEvent(level: LogEventLevel.Information));
+ sink.Emit(Some.LogEvent(level: LogEventLevel.Warning));
+
+ var lines = ReadAllLinesShared(path);
+ Assert.Empty(lines);
+
+ sink.Emit(Some.LogEvent(level: LogEventLevel.Fatal));
+
+ lines = ReadAllLinesShared(path);
+ Assert.Equal(3, lines.Length);
+ }
+ }
+
static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding)
{
using var tmp = TempFolder.ForCaller();
@@ -260,4 +283,21 @@ static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding enco
size = new FileInfo(path).Length;
Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size);
}
+
+ private static string[] ReadAllLinesShared(string path)
+ {
+ // ReadAllLines cannot be used here, as it can't read files even if they are opened with FileShare.Read
+ using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var reader = new StreamReader(fs);
+
+ string? line;
+ List lines = [];
+
+ while ((line = reader.ReadLine()) != null)
+ {
+ lines.Add(line);
+ }
+
+ return [.. lines];
+ }
}