Skip to content

Commit

Permalink
Add trimFileSizeBytes option
Browse files Browse the repository at this point in the history
  • Loading branch information
vatara committed Sep 7, 2018
1 parent f18cb0e commit 6bccc72
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 16 deletions.
26 changes: 18 additions & 8 deletions src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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="trimFileSizeBytes">The size a log file will trimmed to after it reaches fileSizeLimitBytes</param>
/// <param name="buffered">Indicates if flushing 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>
Expand All @@ -64,13 +65,14 @@ public static LoggerConfiguration File(
string outputTemplate,
IFormatProvider formatProvider,
long? fileSizeLimitBytes,
int? trimFileSizeBytes,
LoggingLevelSwitch levelSwitch,
bool buffered,
bool shared,
TimeSpan? flushToDiskInterval)
{
// ReSharper disable once RedundantArgumentDefaultValue
return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes,
return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, trimFileSizeBytes,
levelSwitch, buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false,
null, null);
}
Expand All @@ -81,7 +83,7 @@ public static LoggerConfiguration File(
/// <param name="sinkConfiguration">Logger sink configuration.</param>
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?)"/>
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, int?, LoggingLevelSwitch, bool, bool, TimeSpan?)"/>
/// and specify the outputTemplate parameter instead.
/// </param>
/// <param name="path">Path to the file.</param>
Expand All @@ -92,6 +94,7 @@ 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="trimFileSizeBytes">The size a log file will trimmed to after it reaches fileSizeLimitBytes</param>
/// <param name="buffered">Indicates if flushing 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>
Expand All @@ -105,13 +108,14 @@ public static LoggerConfiguration File(
string path,
LogEventLevel restrictedToMinimumLevel,
long? fileSizeLimitBytes,
int? trimFileSizeBytes,
LoggingLevelSwitch levelSwitch,
bool buffered,
bool shared,
TimeSpan? flushToDiskInterval)
{
// ReSharper disable once RedundantArgumentDefaultValue
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch,
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, trimFileSizeBytes, levelSwitch,
buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null);
}

Expand All @@ -130,6 +134,7 @@ 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="trimFileSizeBytes">The size a log file will trimmed to after it reaches fileSizeLimitBytes</param>
/// <param name="buffered">Indicates if flushing 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>
Expand All @@ -149,6 +154,7 @@ public static LoggerConfiguration File(
string outputTemplate = DefaultOutputTemplate,
IFormatProvider formatProvider = null,
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
int? trimFileSizeBytes = null,
LoggingLevelSwitch levelSwitch = null,
bool buffered = false,
bool shared = false,
Expand All @@ -163,7 +169,7 @@ public static LoggerConfiguration File(
if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate));

var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes,
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, trimFileSizeBytes,
levelSwitch, buffered, shared, flushToDiskInterval,
rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding);
}
Expand All @@ -174,7 +180,7 @@ public static LoggerConfiguration File(
/// <param name="sinkConfiguration">Logger sink configuration.</param>
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding)"/>
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, int?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding)"/>
/// and specify the outputTemplate parameter instead.
/// </param>
/// <param name="path">Path to the file.</param>
Expand All @@ -185,6 +191,7 @@ 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="trimFileSizeBytes">The size a log file will trimmed to after it reaches fileSizeLimitBytes</param>
/// <param name="buffered">Indicates if flushing 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>
Expand All @@ -203,6 +210,7 @@ public static LoggerConfiguration File(
string path,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
int? trimFileSizeBytes = null,
LoggingLevelSwitch levelSwitch = null,
bool buffered = false,
bool shared = false,
Expand All @@ -212,7 +220,7 @@ public static LoggerConfiguration File(
int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
Encoding encoding = null)
{
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch,
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, trimFileSizeBytes, levelSwitch,
buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit);
}

Expand Down Expand Up @@ -269,7 +277,7 @@ public static LoggerConfiguration File(
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true,
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, null, levelSwitch, false, true,
false, null, null, RollingInterval.Infinite, false, null);
}

Expand All @@ -279,6 +287,7 @@ static LoggerConfiguration ConfigureFile(
string path,
LogEventLevel restrictedToMinimumLevel,
long? fileSizeLimitBytes,
int? trimFileSizeBytes,
LoggingLevelSwitch levelSwitch,
bool buffered,
bool propagateExceptions,
Expand All @@ -293,6 +302,7 @@ static LoggerConfiguration ConfigureFile(
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
if (path == null) throw new ArgumentNullException(nameof(path));
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.", nameof(fileSizeLimitBytes));
if (trimFileSizeBytes.HasValue && trimFileSizeBytes < 0) throw new ArgumentException("Negative value provided; trim file size must be non-negative.", nameof(trimFileSizeBytes));
if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit));
if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered));

Expand All @@ -313,7 +323,7 @@ static LoggerConfiguration ConfigureFile(
}
else
{
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
sink = new FileSink(path, formatter, fileSizeLimitBytes, trimFileSizeBytes, buffered: buffered);
}
#pragma warning restore 618
}
Expand Down
62 changes: 55 additions & 7 deletions src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,41 @@ namespace Serilog.Sinks.File
[Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File()` instead.")]
public sealed class FileSink : IFileSink, IDisposable
{
readonly TextWriter _output;
readonly FileStream _underlyingStream;
TextWriter _output;
FileStream _underlyingStream;
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly int? _trimFileSizeBytes;
readonly bool _buffered;
readonly object _syncRoot = new object();
readonly WriteCountingStream _countingStreamWrapper;
WriteCountingStream _countingStreamWrapper;

readonly string _path;
readonly Encoding _encoding;

/// <summary>Construct a <see cref="FileSink"/>.</summary>
/// <param name="path">Path to the file.</param>
/// <param name="textFormatter">Formatter used to convert log events to text.</param>
/// <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="trimFileSizeBytes">The size a log file will trimmed to after it reaches fileSizeLimitBytes</param>
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
/// <exception cref="IOException"></exception>
public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false)
public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, int? trimFileSizeBytes = null, Encoding encoding = null, bool buffered = false)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
if (trimFileSizeBytes.HasValue && trimFileSizeBytes < 0) throw new ArgumentException("Negative value provided; trim file size must be non-negative.");

_textFormatter = textFormatter;
_fileSizeLimitBytes = fileSizeLimitBytes;
_trimFileSizeBytes = trimFileSizeBytes;
_buffered = buffered;

var directory = Path.GetDirectoryName(path);
Expand All @@ -62,13 +69,45 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
Directory.CreateDirectory(directory);
}

Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
_path = path;
_encoding = encoding;
CreateOutput();
}

long LogFileSize()
{
return new FileInfo(_path).Length;
}

private void CreateOutput()
{
Stream outputStream = _underlyingStream = System.IO.File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.Read);
if (_fileSizeLimitBytes != null)
{
outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream);
}

_output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
_output = new StreamWriter(outputStream, _encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}

private void TrimLog()
{
_output.Dispose();

byte[] appendBuffer;
using (var appendFileStream = new FileStream(_path, FileMode.Open))
{
appendFileStream.Seek(-_trimFileSizeBytes.Value, SeekOrigin.End);
appendBuffer = new byte[_trimFileSizeBytes.Value];
appendFileStream.Read(appendBuffer, 0, _trimFileSizeBytes.Value);
}

using (var truncateFileStream = new FileStream(_path, FileMode.Truncate))
{
truncateFileStream.Write(appendBuffer, 0, _trimFileSizeBytes.Value);
}

CreateOutput();
}

bool IFileSink.EmitOrOverflow(LogEvent logEvent)
Expand All @@ -78,8 +117,17 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
{
if (_fileSizeLimitBytes != null)
{
if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes.Value)
if (_trimFileSizeBytes != null)
{
if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes)
{
TrimLog();
}
}
else if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes.Value)
{
return false;
}
}

_textFormatter.Format(logEvent, _output);
Expand Down
2 changes: 1 addition & 1 deletion src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ void OpenFile(DateTime now, int? minSequence = null)
{
_currentFile = _shared ?
(IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) :
new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered);
new FileSink(path, _textFormatter, _fileSizeLimitBytes, null, _encoding, _buffered);
_currentFileSequence = sequence;
}
catch (IOException ex)
Expand Down

0 comments on commit 6bccc72

Please sign in to comment.