From 6f15ef545e9332bf270c88a6ef32907a79886181 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 11:24:12 +1000 Subject: [PATCH 1/3] Fixes #18 - flush to disk option; re-enables (and fixes) shared file sink tests. --- .../FileLoggerConfigurationExtensions.cs | 38 ++++++++--- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 24 ++++--- .../Sinks/File/IFlushableFileSink.cs | 13 ++++ .../Sinks/File/PeriodicFlushToDiskSink`1.cs | 66 +++++++++++++++++++ .../Sinks/File/SharedFileSink.cs | 53 ++++++++------- .../Sinks/File/WriteCountingStream.cs | 1 + src/Serilog.Sinks.File/project.json | 5 +- .../FileLoggerConfigurationExtensionsTests.cs | 29 ++++++++ .../Serilog.Sinks.File.Tests.xproj | 3 + .../SharedFileSinkTests.cs | 2 - test/Serilog.Sinks.File.Tests/project.json | 6 +- 11 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs create mode 100644 src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 8cffba0..229f273 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -48,6 +48,7 @@ public static class FileLoggerConfigurationExtensions /// Indicates if flushing 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. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -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); } /// @@ -75,7 +77,7 @@ public static LoggerConfiguration File( /// Logger sink configuration. /// A formatter, such as , to convert the log events into /// text for the file. If control of regular text formatting is required, use the other - /// overload of + /// overload of /// and specify the outputTemplate parameter instead. /// /// Path to the file. @@ -89,6 +91,7 @@ public static LoggerConfiguration File( /// Indicates if flushing 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. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -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); } /// @@ -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)); @@ -192,12 +197,28 @@ static LoggerConfiguration ConfigureFile( #if ATOMIC_APPEND if (shared) { - sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); + var sfs = new SharedFileSink(path, formatter, fileSizeLimitBytes); + if (flushToDiskInterval.HasValue) + { + sink = new PeriodicFlushToDiskSink(sfs, flushToDiskInterval.Value); + } + else + { + sink = sfs; + } } else { #endif - sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); + var fs = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); + if (flushToDiskInterval.HasValue) + { + sink = new PeriodicFlushToDiskSink(fs, flushToDiskInterval.Value); + } + else + { + sink = fs; + } #if ATOMIC_APPEND } #endif @@ -212,6 +233,7 @@ static LoggerConfiguration ConfigureFile( return addSink(new NullSink(), LevelAlias.Maximum, null); } + return addSink(sink, restrictedToMinimumLevel, levelSwitch); } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 3af8386..610f9e7 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -24,9 +24,10 @@ namespace Serilog.Sinks.File /// /// Write log events to a disk file. /// - 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; @@ -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)); } /// @@ -91,10 +92,17 @@ public void Emit(LogEvent logEvent) } } - /// - /// Performs application-defined tasks associated with freeing, releasing, or - /// resetting unmanaged resources. - /// + /// public void Dispose() => _output.Dispose(); + + /// + public void FlushToDisk() + { + lock (_syncRoot) + { + _output.Flush(); + _underlyingStream.Flush(true); + } + } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs new file mode 100644 index 0000000..c74727e --- /dev/null +++ b/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs @@ -0,0 +1,13 @@ +namespace Serilog.Sinks.File +{ + /// + /// Supported by (file-based) sinks that can be explicitly flushed. + /// + public interface IFlushableFileSink + { + /// + /// Flush buffered contents to disk. + /// + void FlushToDisk(); + } +} diff --git a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs new file mode 100644 index 0000000..1c239ab --- /dev/null +++ b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading; +using Serilog.Core; +using Serilog.Debugging; +using Serilog.Events; + +namespace Serilog.Sinks.File +{ + /// + /// A sink wrapper that periodically flushes the wrapped sink to disk. + /// + /// The type of the wrapped sink. + public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable + where TSink : ILogEventSink, IFlushableFileSink + { + readonly TSink _sink; + readonly Timer _timer; + int _flushRequired; + + /// + /// Construct a that wraps + /// and flushes it at the specified . + /// + /// The sink to wrap. + /// The interval at which to flush the underlying sink. + /// + public PeriodicFlushToDiskSink(TSink sink, TimeSpan flushInterval) + { + if (sink == null) throw new ArgumentNullException(nameof(sink)); + + _sink = sink; + _timer = new Timer(_ => FlushToDisk(), null, flushInterval, flushInterval); + } + + /// + public void Emit(LogEvent logEvent) + { + _sink.Emit(logEvent); + Interlocked.Exchange(ref _flushRequired, 1); + } + + /// + public void Dispose() + { + _timer.Dispose(); + (_sink as IDisposable)?.Dispose(); + } + + void FlushToDisk() + { + try + { + if (Interlocked.CompareExchange(ref _flushRequired, 0, 1) == 1) + { + // May throw ObjectDisposedException, since we're not trying to synchronize + // anything here in the wrapper. + _sink.FlushToDisk(); + } + } + catch (Exception ex) + { + SelfLog.WriteLine("Could not flush the underlying sink to disk: {0}", ex); + } + } + } +} diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs index a8af612..113f608 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs @@ -27,7 +27,7 @@ namespace Serilog.Sinks.File /// /// Write log events to a disk file. /// - public sealed class SharedFileSink : ILogEventSink, IDisposable + public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposable { readonly MemoryStream _writeBuffer; readonly string _path; @@ -35,7 +35,6 @@ public sealed class SharedFileSink : ILogEventSink, IDisposable 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; @@ -53,11 +52,13 @@ public sealed class SharedFileSink : ILogEventSink, IDisposable /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. /// - 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; @@ -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)); } /// @@ -96,12 +93,6 @@ 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 @@ -109,7 +100,7 @@ public void Emit(LogEvent logEvent) _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; @@ -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(); } @@ -143,11 +144,19 @@ public void Emit(LogEvent logEvent) } } - /// - /// Performs application-defined tasks associated with freeing, releasing, or - /// resetting unmanaged resources. - /// + + /// public void Dispose() => _fileOutput.Dispose(); + + /// + public void FlushToDisk() + { + lock (_syncRoot) + { + _output.Flush(); + _fileOutput.Flush(true); + } + } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs index ae44fa4..f0ec6a9 100644 --- a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs +++ b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs @@ -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; } diff --git a/src/Serilog.Sinks.File/project.json b/src/Serilog.Sinks.File/project.json index a0463ce..6d918a7 100644 --- a/src/Serilog.Sinks.File/project.json +++ b/src/Serilog.Sinks.File/project.json @@ -1,5 +1,5 @@ { - "version": "3.0.2-*", + "version": "3.1.0-*", "description": "Write Serilog events to a text file in plain or JSON format.", "authors": [ "Serilog Contributors" ], "packOptions": { @@ -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" } } } diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index 8dbbdbc..78c35b3 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Serilog; using Serilog.Sinks.File.Tests.Support; using Serilog.Tests.Support; @@ -51,5 +52,33 @@ public void WhenAuditingLoggingExceptionsPropagate() Assert.IsType(ex.GetBaseException()); } } + + [Fact] + public void WhenFlushingToDiskReportedFileSinkCanBeCreatedAndDisposed() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(tmp.AllocateFilename(), flushToDiskInterval: TimeSpan.FromMilliseconds(500)) + .CreateLogger()) + { + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + } + +#if ATOMIC_APPEND + [Fact] + public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(tmp.AllocateFilename(), shared: true, flushToDiskInterval: TimeSpan.FromMilliseconds(500)) + .CreateLogger()) + { + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + } +#endif } } diff --git a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.xproj b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.xproj index 9e7949e..3234f8a 100644 --- a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.xproj +++ b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.xproj @@ -14,5 +14,8 @@ 2.0 + + + \ No newline at end of file diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index ec325fc..d0bd751 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -1,11 +1,9 @@ #if ATOMIC_APPEND -using System; using System.IO; using Xunit; using Serilog.Formatting.Json; using Serilog.Sinks.File.Tests.Support; -using Serilog.Sinks.File; using Serilog.Tests.Support; namespace Serilog.Sinks.File.Tests diff --git a/test/Serilog.Sinks.File.Tests/project.json b/test/Serilog.Sinks.File.Tests/project.json index 8b7feb8..1fa084d 100644 --- a/test/Serilog.Sinks.File.Tests/project.json +++ b/test/Serilog.Sinks.File.Tests/project.json @@ -6,7 +6,6 @@ "dotnet-test-xunit": "1.0.0-rc2-build10025" }, "frameworks": { - "net4.5.2": {}, "netcoreapp1.0": { "dependencies": { "Microsoft.NETCore.App": { @@ -18,6 +17,11 @@ "dnxcore50", "portable-net45+win8" ] + }, + "net4.5.2": { + "buildOptions": { + "define": ["ATOMIC_APPEND"] + } } } } From 3fbaaa16d254332c1457951fb5a8d975dae78b23 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Wed, 5 Oct 2016 11:40:57 +1000 Subject: [PATCH 2/3] Update to the latest Serilog dependency --- src/Serilog.Sinks.File/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/project.json b/src/Serilog.Sinks.File/project.json index 6d918a7..82265a8 100644 --- a/src/Serilog.Sinks.File/project.json +++ b/src/Serilog.Sinks.File/project.json @@ -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", From 5fd5494fbc807b0cb7228af472b1e0d1b54478ed Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 6 Oct 2016 09:37:24 +1000 Subject: [PATCH 3/3] Made PeriodicFlushToDiskSink non-generic --- .../FileLoggerConfigurationExtensions.cs | 24 ++++------------ ...skSink`1.cs => PeriodicFlushToDiskSink.cs} | 28 ++++++++++++------- 2 files changed, 24 insertions(+), 28 deletions(-) rename src/Serilog.Sinks.File/Sinks/File/{PeriodicFlushToDiskSink`1.cs => PeriodicFlushToDiskSink.cs} (60%) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 229f273..571f414 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -197,28 +197,12 @@ static LoggerConfiguration ConfigureFile( #if ATOMIC_APPEND if (shared) { - var sfs = new SharedFileSink(path, formatter, fileSizeLimitBytes); - if (flushToDiskInterval.HasValue) - { - sink = new PeriodicFlushToDiskSink(sfs, flushToDiskInterval.Value); - } - else - { - sink = sfs; - } + sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); } else { #endif - var fs = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); - if (flushToDiskInterval.HasValue) - { - sink = new PeriodicFlushToDiskSink(fs, flushToDiskInterval.Value); - } - else - { - sink = fs; - } + sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); #if ATOMIC_APPEND } #endif @@ -233,6 +217,10 @@ static LoggerConfiguration ConfigureFile( return addSink(new NullSink(), LevelAlias.Maximum, null); } + if (flushToDiskInterval.HasValue) + { + sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value); + } return addSink(sink, restrictedToMinimumLevel, levelSwitch); } diff --git a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs similarity index 60% rename from src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs rename to src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs index 1c239ab..cafb72e 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink`1.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs @@ -9,27 +9,35 @@ namespace Serilog.Sinks.File /// /// A sink wrapper that periodically flushes the wrapped sink to disk. /// - /// The type of the wrapped sink. - public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable - where TSink : ILogEventSink, IFlushableFileSink + public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable { - readonly TSink _sink; + readonly ILogEventSink _sink; readonly Timer _timer; int _flushRequired; /// - /// Construct a that wraps + /// Construct a that wraps /// and flushes it at the specified . /// /// The sink to wrap. /// The interval at which to flush the underlying sink. /// - public PeriodicFlushToDiskSink(TSink sink, TimeSpan flushInterval) + public PeriodicFlushToDiskSink(ILogEventSink sink, TimeSpan flushInterval) { if (sink == null) throw new ArgumentNullException(nameof(sink)); _sink = sink; - _timer = new Timer(_ => FlushToDisk(), null, flushInterval, flushInterval); + + 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)); + } } /// @@ -46,7 +54,7 @@ public void Dispose() (_sink as IDisposable)?.Dispose(); } - void FlushToDisk() + void FlushToDisk(IFlushableFileSink flushable) { try { @@ -54,12 +62,12 @@ void FlushToDisk() { // May throw ObjectDisposedException, since we're not trying to synchronize // anything here in the wrapper. - _sink.FlushToDisk(); + flushable.FlushToDisk(); } } catch (Exception ex) { - SelfLog.WriteLine("Could not flush the underlying sink to disk: {0}", ex); + SelfLog.WriteLine("{0} could not flush the underlying sink to disk: {1}", typeof(PeriodicFlushToDiskSink), ex); } } }