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]; + } }