diff --git a/.gitignore b/.gitignore index 94420dc..483adfa 100644 --- a/.gitignore +++ b/.gitignore @@ -234,3 +234,4 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +example/Sample/log.txt diff --git a/example/Sample/Program.cs b/example/Sample/Program.cs index 99a158b..89e6c95 100644 --- a/example/Sample/Program.cs +++ b/example/Sample/Program.cs @@ -11,12 +11,12 @@ public static void Main(string[] args) { SelfLog.Enable(Console.Out); + var sw = System.Diagnostics.Stopwatch.StartNew(); + Log.Logger = new LoggerConfiguration() .WriteTo.File("log.txt") .CreateLogger(); - var sw = System.Diagnostics.Stopwatch.StartNew(); - for (var i = 0; i < 1000000; ++i) { Log.Information("Hello, file logger!"); diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 571f414..353e6c6 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -180,32 +180,20 @@ 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"); - - if (shared) - { -#if ATOMIC_APPEND - if (buffered) - throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); -#else - throw new NotSupportedException("File sharing is not supported on this platform."); -#endif - } + if (shared && buffered) + throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); ILogEventSink sink; try { -#if ATOMIC_APPEND if (shared) { sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); } else { -#endif sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); -#if ATOMIC_APPEND } -#endif } catch (Exception ex) { diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs similarity index 98% rename from src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs rename to src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index bbb5142..4ea5022 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -52,8 +52,7 @@ public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposa /// 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)); diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs new file mode 100644 index 0000000..d3cf809 --- /dev/null +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs @@ -0,0 +1,162 @@ +// Copyright 2013-2016 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if OS_MUTEX + +using System; +using System.IO; +using System.Text; +using Serilog.Core; +using Serilog.Events; +using Serilog.Formatting; +using System.Threading; +using Serilog.Debugging; + +namespace Serilog.Sinks.File +{ + /// + /// Write log events to a disk file. + /// + public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposable + { + readonly TextWriter _output; + readonly FileStream _underlyingStream; + readonly ITextFormatter _textFormatter; + readonly long? _fileSizeLimitBytes; + readonly object _syncRoot = new object(); + + const string MutexNameSuffix = ".serilog"; + const int MutexWaitTimeout = 10000; + readonly Mutex _mutex; + + /// Construct a . + /// Path to the file. + /// Formatter used to convert log events to text. + /// 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. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// 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) + { + 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"); + + _textFormatter = textFormatter; + _fileSizeLimitBytes = fileSizeLimitBytes; + + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var mutexName = Path.GetFullPath(path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix; + _mutex = new Mutex(false, mutexName); + _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + _output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + /// + /// Emit the provided log event to the sink. + /// + /// The log event to write. + public void Emit(LogEvent logEvent) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + + lock (_syncRoot) + { + if (!TryAcquireMutex()) + return; + + try + { + _underlyingStream.Seek(0, SeekOrigin.End); + if (_fileSizeLimitBytes != null) + { + if (_underlyingStream.Length >= _fileSizeLimitBytes.Value) + return; + } + + _textFormatter.Format(logEvent, _output); + _output.Flush(); + _underlyingStream.Flush(); + } + finally + { + ReleaseMutex(); + } + } + } + + /// + public void Dispose() + { + lock (_syncRoot) + { + _output.Dispose(); + _mutex.Dispose(); + } + } + + /// + public void FlushToDisk() + { + lock (_syncRoot) + { + if (!TryAcquireMutex()) + return; + + try + { + _underlyingStream.Flush(true); + } + finally + { + ReleaseMutex(); + } + } + } + + bool TryAcquireMutex() + { + try + { + if (!_mutex.WaitOne(MutexWaitTimeout)) + { + SelfLog.WriteLine("Shared file mutex could not be acquired within {0} ms", MutexWaitTimeout); + return false; + } + } + catch (AbandonedMutexException) + { + SelfLog.WriteLine("Inherited shared file mutex after abandonment by another process"); + } + + return true; + } + + void ReleaseMutex() + { + _mutex.ReleaseMutex(); + } + } +} + +#endif diff --git a/src/Serilog.Sinks.File/project.json b/src/Serilog.Sinks.File/project.json index 8adf2e2..73378e9 100644 --- a/src/Serilog.Sinks.File/project.json +++ b/src/Serilog.Sinks.File/project.json @@ -20,12 +20,14 @@ "buildOptions": { "define": [ "ATOMIC_APPEND" ] } }, "netstandard1.3": { + "buildOptions": { "define": [ "OS_MUTEX" ] }, "dependencies": { "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.Threading.Timer": "4.0.1" + "System.Threading.Timer": "4.0.1", + "System.Threading": "4.0.11" } } } diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index d0bd751..f63eac1 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -1,6 +1,4 @@ -#if ATOMIC_APPEND - -using System.IO; +using System.IO; using Xunit; using Serilog.Formatting.Json; using Serilog.Sinks.File.Tests.Support; @@ -102,5 +100,3 @@ public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() } } } - -#endif diff --git a/test/Serilog.Sinks.File.Tests/project.json b/test/Serilog.Sinks.File.Tests/project.json index 1fa084d..3f14b0e 100644 --- a/test/Serilog.Sinks.File.Tests/project.json +++ b/test/Serilog.Sinks.File.Tests/project.json @@ -19,9 +19,6 @@ ] }, "net4.5.2": { - "buildOptions": { - "define": ["ATOMIC_APPEND"] - } } } }