Skip to content
Open
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
2 changes: 1 addition & 1 deletion Directory.Version.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<VersionPrefix>7.0.1</VersionPrefix>
<VersionPrefix>8.0.0</VersionPrefix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
24 changes: 12 additions & 12 deletions src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public static class FileLoggerConfigurationExtensions
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <returns>Configuration object allowing method chaining.</returns>
Expand Down Expand Up @@ -89,8 +89,8 @@ public static LoggerConfiguration File(
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <returns>Configuration object allowing method chaining.</returns>
Expand Down Expand Up @@ -126,8 +126,8 @@ public static LoggerConfiguration File(
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
Expand Down Expand Up @@ -175,8 +175,8 @@ public static LoggerConfiguration File(
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
Expand Down Expand Up @@ -221,8 +221,8 @@ public static LoggerConfiguration File(
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
Expand Down Expand Up @@ -291,8 +291,8 @@ public static LoggerConfiguration File(
/// <param name="fileSizeLimitBytes">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.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <param name="buffered">Indicates if flushing of non-fatal <see cref="LogEventLevel.Fatal"/> events to the output file
/// can be buffered or not. The default is false.</param>
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
Expand Down
5 changes: 4 additions & 1 deletion src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 43 additions & 3 deletions test/Serilog.Sinks.File.Tests/FileSinkTests.cs
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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();
Expand All @@ -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<string> lines = [];

while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}

return [.. lines];
}
}