Skip to content

Commit bce2b62

Browse files
committed
Fixes serilog/serilog-sinks-rollingfile#37 - multiprocess sharing on .NET Core
1 parent 77eed1b commit bce2b62

File tree

8 files changed

+154
-18
lines changed

8 files changed

+154
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,4 @@ _Pvt_Extensions
234234

235235
# FAKE - F# Make
236236
.fake/
237+
example/Sample/log.txt

example/Sample/Program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ public static void Main(string[] args)
1111
{
1212
SelfLog.Enable(Console.Out);
1313

14+
var sw = System.Diagnostics.Stopwatch.StartNew();
15+
1416
Log.Logger = new LoggerConfiguration()
15-
.WriteTo.File("log.txt")
17+
.WriteTo.File("log.txt", shared: true)
1618
.CreateLogger();
1719

18-
var sw = System.Diagnostics.Stopwatch.StartNew();
19-
2020
for (var i = 0; i < 1000000; ++i)
2121
{
2222
Log.Information("Hello, file logger!");

src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ static LoggerConfiguration ConfigureFile(
183183

184184
if (shared)
185185
{
186-
#if ATOMIC_APPEND
186+
#if SHARING
187187
if (buffered)
188188
throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered));
189189
#else
@@ -194,7 +194,7 @@ static LoggerConfiguration ConfigureFile(
194194
ILogEventSink sink;
195195
try
196196
{
197-
#if ATOMIC_APPEND
197+
#if SHARING
198198
if (shared)
199199
{
200200
sink = new SharedFileSink(path, formatter, fileSizeLimitBytes);
@@ -203,7 +203,7 @@ static LoggerConfiguration ConfigureFile(
203203
{
204204
#endif
205205
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
206-
#if ATOMIC_APPEND
206+
#if SHARING
207207
}
208208
#endif
209209
}

src/Serilog.Sinks.File/Sinks/File/SharedFileSink.cs renamed to src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposa
5252
/// <returns>Configuration object allowing method chaining.</returns>
5353
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
5454
/// <exception cref="IOException"></exception>
55-
public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes,
56-
Encoding encoding = null)
55+
public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null)
5756
{
5857
if (path == null) throw new ArgumentNullException(nameof(path));
5958
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2013-2016 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#if OS_MUTEX
16+
17+
using System;
18+
using System.IO;
19+
using System.Text;
20+
using Serilog.Core;
21+
using Serilog.Events;
22+
using Serilog.Formatting;
23+
using System.Threading;
24+
25+
namespace Serilog.Sinks.File
26+
{
27+
/// <summary>
28+
/// Write log events to a disk file.
29+
/// </summary>
30+
public sealed class SharedFileSink : ILogEventSink, IFlushableFileSink, IDisposable
31+
{
32+
readonly TextWriter _output;
33+
readonly FileStream _underlyingStream;
34+
readonly ITextFormatter _textFormatter;
35+
readonly long? _fileSizeLimitBytes;
36+
readonly object _syncRoot = new object();
37+
38+
const string MutexNameSuffix = ".serilog";
39+
readonly Mutex _mutex;
40+
41+
/// <summary>Construct a <see cref="FileSink"/>.</summary>
42+
/// <param name="path">Path to the file.</param>
43+
/// <param name="textFormatter">Formatter used to convert log events to text.</param>
44+
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
45+
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
46+
/// will be written in full even if it exceeds the limit.</param>
47+
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
48+
/// <returns>Configuration object allowing method chaining.</returns>
49+
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
50+
/// <exception cref="IOException"></exception>
51+
public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null)
52+
{
53+
if (path == null) throw new ArgumentNullException(nameof(path));
54+
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
55+
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0)
56+
throw new ArgumentException("Negative value provided; file size limit must be non-negative");
57+
58+
_textFormatter = textFormatter;
59+
_fileSizeLimitBytes = fileSizeLimitBytes;
60+
61+
var directory = Path.GetDirectoryName(path);
62+
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
63+
{
64+
Directory.CreateDirectory(directory);
65+
}
66+
67+
// Backslash is special on Windows
68+
_mutex = new Mutex(false, path.Replace('\\', ':') + MutexNameSuffix);
69+
_underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
70+
_output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
71+
}
72+
73+
/// <summary>
74+
/// Emit the provided log event to the sink.
75+
/// </summary>
76+
/// <param name="logEvent">The log event to write.</param>
77+
public void Emit(LogEvent logEvent)
78+
{
79+
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
80+
81+
lock (_syncRoot)
82+
{
83+
_mutex.WaitOne();
84+
try
85+
{
86+
_underlyingStream.Seek(0, SeekOrigin.End);
87+
if (_fileSizeLimitBytes != null)
88+
{
89+
if (_underlyingStream.Length >= _fileSizeLimitBytes.Value)
90+
return;
91+
}
92+
93+
_textFormatter.Format(logEvent, _output);
94+
_output.Flush();
95+
_underlyingStream.Flush();
96+
}
97+
finally
98+
{
99+
_mutex.ReleaseMutex();
100+
}
101+
}
102+
}
103+
104+
/// <inheritdoc />
105+
public void Dispose()
106+
{
107+
lock (_syncRoot)
108+
{
109+
_output.Dispose();
110+
}
111+
}
112+
113+
/// <inheritdoc />
114+
public void FlushToDisk()
115+
{
116+
lock (_syncRoot)
117+
{
118+
_mutex.WaitOne();
119+
try
120+
{
121+
_underlyingStream.Flush(true);
122+
}
123+
finally
124+
{
125+
_mutex.ReleaseMutex();
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
#endif

src/Serilog.Sinks.File/project.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,26 @@
1717
},
1818
"frameworks": {
1919
"net4.5": {
20-
"buildOptions": { "define": [ "ATOMIC_APPEND" ] }
20+
"buildOptions": { "define": [ "SHARING", "ATOMIC_APPEND" ] }
21+
},
22+
"netcoreapp1.0": {
23+
"buildOptions": { "define": [ "SHARING", "OS_MUTEX" ] },
24+
"dependencies": {
25+
"System.IO": "4.1.0",
26+
"System.IO.FileSystem": "4.0.1",
27+
"System.IO.FileSystem.Primitives": "4.0.1",
28+
"System.Text.Encoding.Extensions": "4.0.11",
29+
"System.Threading.Timer": "4.0.1",
30+
"System.Threading": "4.0.11"
31+
}
2132
},
2233
"netstandard1.3": {
2334
"dependencies": {
2435
"System.IO": "4.1.0",
2536
"System.IO.FileSystem": "4.0.1",
2637
"System.IO.FileSystem.Primitives": "4.0.1",
2738
"System.Text.Encoding.Extensions": "4.0.11",
28-
"System.Threading.Timer": "4.0.1"
39+
"System.Threading.Timer": "4.0.1"
2940
}
3041
}
3142
}

test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
#if ATOMIC_APPEND
2-
3-
using System.IO;
1+
using System.IO;
42
using Xunit;
53
using Serilog.Formatting.Json;
64
using Serilog.Sinks.File.Tests.Support;
@@ -102,5 +100,3 @@ public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted()
102100
}
103101
}
104102
}
105-
106-
#endif

test/Serilog.Sinks.File.Tests/project.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
]
2020
},
2121
"net4.5.2": {
22-
"buildOptions": {
23-
"define": ["ATOMIC_APPEND"]
24-
}
2522
}
2623
}
2724
}

0 commit comments

Comments
 (0)