Skip to content

Commit

Permalink
Merge pull request #21 from serilog/dev
Browse files Browse the repository at this point in the history
Release 3.1.0
  • Loading branch information
nblumhardt authored Oct 9, 2016
2 parents 83a220c + ea75e90 commit 36b3217
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 44 deletions.
22 changes: 16 additions & 6 deletions src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static class FileLoggerConfigurationExtensions
/// <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>
/// <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>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
Expand All @@ -59,14 +60,15 @@ public static LoggerConfiguration File(
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
LoggingLevelSwitch levelSwitch = null,
bool buffered = false,
bool shared = false)
bool shared = false,
TimeSpan? flushToDiskInterval = null)
{
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
if (path == null) throw new ArgumentNullException(nameof(path));
if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate));

var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared);
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval);
}

/// <summary>
Expand All @@ -75,7 +77,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)"/>
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?)"/>
/// and specify the outputTemplate parameter instead.
/// </param>
/// <param name="path">Path to the file.</param>
Expand All @@ -89,6 +91,7 @@ public static LoggerConfiguration File(
/// <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>
/// <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>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
Expand All @@ -99,9 +102,10 @@ public static LoggerConfiguration File(
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
LoggingLevelSwitch levelSwitch = null,
bool buffered = false,
bool shared = false)
bool shared = false,
TimeSpan? flushToDiskInterval = null)
{
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared);
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval);
}

/// <summary>
Expand Down Expand Up @@ -169,7 +173,8 @@ static LoggerConfiguration ConfigureFile(
LoggingLevelSwitch levelSwitch = null,
bool buffered = false,
bool propagateExceptions = false,
bool shared = false)
bool shared = false,
TimeSpan? flushToDiskInterval = null)
{
if (addSink == null) throw new ArgumentNullException(nameof(addSink));
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
Expand Down Expand Up @@ -212,6 +217,11 @@ static LoggerConfiguration ConfigureFile(
return addSink(new NullSink(), LevelAlias.Maximum, null);
}

if (flushToDiskInterval.HasValue)
{
sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value);
}

return addSink(sink, restrictedToMinimumLevel, levelSwitch);
}
}
Expand Down
32 changes: 23 additions & 9 deletions src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ namespace Serilog.Sinks.File
/// <summary>
/// Write log events to a disk file.
/// </summary>
public sealed class FileSink : ILogEventSink, IDisposable
public sealed class FileSink : ILogEventSink, IFlushableFileSink, IDisposable
{
readonly TextWriter _output;
readonly FileStream _underlyingStream;
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly bool _buffered;
Expand Down Expand Up @@ -61,13 +62,13 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
Directory.CreateDirectory(directory);
}

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

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

/// <summary>
Expand All @@ -91,10 +92,23 @@ public void Emit(LogEvent logEvent)
}
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose() => _output.Dispose();
/// <inheritdoc />
public void Dispose()
{
lock (_syncRoot)
{
_output.Dispose();
}
}

/// <inheritdoc />
public void FlushToDisk()
{
lock (_syncRoot)
{
_output.Flush();
_underlyingStream.Flush(true);
}
}
}
}
13 changes: 13 additions & 0 deletions src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Serilog.Sinks.File
{
/// <summary>
/// Supported by (file-based) sinks that can be explicitly flushed.
/// </summary>
public interface IFlushableFileSink
{
/// <summary>
/// Flush buffered contents to disk.
/// </summary>
void FlushToDisk();
}
}
74 changes: 74 additions & 0 deletions src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Threading;
using Serilog.Core;
using Serilog.Debugging;
using Serilog.Events;

namespace Serilog.Sinks.File
{
/// <summary>
/// A sink wrapper that periodically flushes the wrapped sink to disk.
/// </summary>
public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable
{
readonly ILogEventSink _sink;
readonly Timer _timer;
int _flushRequired;

/// <summary>
/// Construct a <see cref="PeriodicFlushToDiskSink"/> that wraps
/// <paramref name="sink"/> and flushes it at the specified <paramref name="flushInterval"/>.
/// </summary>
/// <param name="sink">The sink to wrap.</param>
/// <param name="flushInterval">The interval at which to flush the underlying sink.</param>
/// <exception cref="ArgumentNullException"></exception>
public PeriodicFlushToDiskSink(ILogEventSink sink, TimeSpan flushInterval)
{
if (sink == null) throw new ArgumentNullException(nameof(sink));

_sink = sink;

var flushable = sink as IFlushableFileSink;
if (flushable != null)
{
_timer = new Timer(_ => FlushToDisk(flushable), null, flushInterval, flushInterval);
}
else
{
_timer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
SelfLog.WriteLine("{0} configured to flush {1}, but {2} not implemented", typeof(PeriodicFlushToDiskSink), sink, nameof(IFlushableFileSink));
}
}

/// <inheritdoc />
public void Emit(LogEvent logEvent)
{
_sink.Emit(logEvent);
Interlocked.Exchange(ref _flushRequired, 1);
}

/// <inheritdoc />
public void Dispose()
{
_timer.Dispose();
(_sink as IDisposable)?.Dispose();
}

void FlushToDisk(IFlushableFileSink flushable)
{
try
{
if (Interlocked.CompareExchange(ref _flushRequired, 0, 1) == 1)
{
// May throw ObjectDisposedException, since we're not trying to synchronize
// anything here in the wrapper.
flushable.FlushToDisk();
}
}
catch (Exception ex)
{
SelfLog.WriteLine("{0} could not flush the underlying sink to disk: {1}", typeof(PeriodicFlushToDiskSink), ex);
}
}
}
}
61 changes: 38 additions & 23 deletions src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@ namespace Serilog.Sinks.File
/// <summary>
/// Write log events to a disk file.
/// </summary>
public sealed class SharedFileSink : ILogEventSink, IDisposable
public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposable
{
readonly MemoryStream _writeBuffer;
readonly string _path;
readonly TextWriter _output;
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly object _syncRoot = new object();
readonly FileInfo _fileInfo;

// The stream is reopened with a larger buffer if atomic writes beyond the current buffer size are needed.
FileStream _fileOutput;
Expand All @@ -53,11 +52,13 @@ public sealed class SharedFileSink : ILogEventSink, IDisposable
/// <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 SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null)
public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes,
Encoding encoding = null)
{
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 (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
throw new ArgumentException("Negative value provided; file size limit must be non-negative");

_path = path;
_textFormatter = textFormatter;
Expand All @@ -72,20 +73,16 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
// FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND, but that API is not yet
// exposed by .NET Core.
_fileOutput = new FileStream(
path,
path,
FileMode.Append,
FileSystemRights.AppendData,
FileShare.ReadWrite,
_fileStreamBufferLength,
FileOptions.None);

if (_fileSizeLimitBytes != null)
{
_fileInfo = new FileInfo(path);
}

_writeBuffer = new MemoryStream();
_output = new StreamWriter(_writeBuffer, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
_output = new StreamWriter(_writeBuffer,
encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}

/// <summary>
Expand All @@ -96,20 +93,14 @@ public void Emit(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));

if (_fileSizeLimitBytes != null)
{
if (_fileInfo.Length >= _fileSizeLimitBytes.Value)
return;
}

lock (_syncRoot)
{
try
{
_textFormatter.Format(logEvent, _output);
_output.Flush();
var bytes = _writeBuffer.GetBuffer();
var length = (int)_writeBuffer.Length;
var length = (int) _writeBuffer.Length;
if (length > _fileStreamBufferLength)
{
var oldOutput = _fileOutput;
Expand All @@ -126,6 +117,16 @@ public void Emit(LogEvent logEvent)
oldOutput.Dispose();
}

if (_fileSizeLimitBytes != null)
{
try
{
if (_fileOutput.Length >= _fileSizeLimitBytes.Value)
return;
}
catch (FileNotFoundException) { } // Cheaper and more reliable than checking existence
}

_fileOutput.Write(bytes, 0, length);
_fileOutput.Flush();
}
Expand All @@ -143,11 +144,25 @@ public void Emit(LogEvent logEvent)
}
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
/// </summary>
public void Dispose() => _fileOutput.Dispose();

/// <inheritdoc />
public void Dispose()
{
lock (_syncRoot)
{
_fileOutput.Dispose();
}
}

/// <inheritdoc />
public void FlushToDisk()
{
lock (_syncRoot)
{
_output.Flush();
_fileOutput.Flush(true);
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public override void Write(byte[] buffer, int offset, int count)
public override bool CanWrite => true;
public override long Length => _stream.Length;


public override long Position
{
get { return _stream.Position; }
Expand Down
7 changes: 4 additions & 3 deletions src/Serilog.Sinks.File/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.0.1-*",
"version": "3.1.0-*",
"description": "Write Serilog events to a text file in plain or JSON format.",
"authors": [ "Serilog Contributors" ],
"packOptions": {
Expand All @@ -9,7 +9,7 @@
"iconUrl": "http://serilog.net/images/serilog-sink-nuget.png"
},
"dependencies": {
"Serilog": "2.2.0"
"Serilog": "2.3.0"
},
"buildOptions": {
"keyFile": "../../assets/Serilog.snk",
Expand All @@ -24,7 +24,8 @@
"System.IO": "4.1.0",
"System.IO.FileSystem": "4.0.1",
"System.IO.FileSystem.Primitives": "4.0.1",
"System.Text.Encoding.Extensions": "4.0.11"
"System.Text.Encoding.Extensions": "4.0.11",
"System.Threading.Timer": "4.0.1"
}
}
}
Expand Down
Loading

0 comments on commit 36b3217

Please sign in to comment.