Skip to content

Commit 7614553

Browse files
committed
WIP
1 parent b8986cb commit 7614553

27 files changed

+1621
-85
lines changed

example/Sample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public static void Main(string[] args)
1414
var sw = System.Diagnostics.Stopwatch.StartNew();
1515

1616
Log.Logger = new LoggerConfiguration()
17-
.WriteTo.File("log.txt")
17+
.WriteTo.File("log.txt", fileSizeLimitBytes: 1000000, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
1818
.CreateLogger();
1919

2020
for (var i = 0; i < 1000000; ++i)

example/Sample/Sample.csproj

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netcoreapp1.0;net45</TargetFrameworks>
4+
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
55
<AssemblyName>Sample</AssemblyName>
66
<OutputType>Exe</OutputType>
77
<PackageId>Sample</PackageId>
8-
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
8+
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
99
</PropertyGroup>
1010

1111
<ItemGroup>
1212
<ProjectReference Include="..\..\src\Serilog.Sinks.File\Serilog.Sinks.File.csproj" />
1313
</ItemGroup>
1414

15-
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
15+
<ItemGroup Condition=" '$(TargetFramework)' == 'net47' ">
1616
<Reference Include="System" />
1717
<Reference Include="Microsoft.CSharp" />
1818
</ItemGroup>
1919

20-
<ItemGroup>
21-
<PackageReference Include="System.Threading" Version="4.3.0" />
22-
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
20+
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
2321
</ItemGroup>
2422

2523
</Project>

src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
using System;
1616
using System.ComponentModel;
17+
using System.Text;
1718
using Serilog.Configuration;
1819
using Serilog.Core;
1920
using Serilog.Debugging;
@@ -30,8 +31,9 @@ namespace Serilog
3031
/// <summary>Extends <see cref="LoggerConfiguration"/> with methods to add file sinks.</summary>
3132
public static class FileLoggerConfigurationExtensions
3233
{
34+
const int DefaultRetainedFileCountLimit = 31; // A long month of logs
3335
const long DefaultFileSizeLimitBytes = 1L * 1024 * 1024 * 1024;
34-
const string DefaultOutputTemplate = "[{Timestamp:o} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}";
36+
const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
3537

3638
/// <summary>
3739
/// Write log events to the specified file.
@@ -44,7 +46,7 @@ public static class FileLoggerConfigurationExtensions
4446
/// to be changed at runtime.</param>
4547
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
4648
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
47-
/// the default is "[{Timestamp:o} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}".</param>
49+
/// the default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".</param>
4850
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
4951
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
5052
/// will be written in full even if it exceeds the limit.</param>
@@ -124,7 +126,7 @@ public static LoggerConfiguration File(
124126
/// to be changed at runtime.</param>
125127
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
126128
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
127-
/// the default is "[{Timestamp:o} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}".</param>
129+
/// the default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".</param>
128130
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
129131
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
130132
/// will be written in full even if it exceeds the limit.</param>
@@ -135,6 +137,9 @@ public static LoggerConfiguration File(
135137
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
136138
/// <param name="rollOnFileSizeLimit">If <code>true</code>, a new file will be created when the file size limit is reached. Filenames
137139
/// will have a number appended in the format <code>_NNNNN</code>, with the first filename given no number.</param>
140+
/// <param name="retainedFileCountLimit">The maximum number of log files that will be retained,
141+
/// including the current log file. For unlimited retention, pass null. The default is 31.</param>
142+
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
138143
/// <returns>Configuration object allowing method chaining.</returns>
139144
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
140145
public static LoggerConfiguration File(
@@ -149,7 +154,9 @@ public static LoggerConfiguration File(
149154
bool shared = false,
150155
TimeSpan? flushToDiskInterval = null,
151156
RollingInterval rollingInterval = RollingInterval.Infinite,
152-
bool rollOnFileSizeLimit = false)
157+
bool rollOnFileSizeLimit = false,
158+
int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
159+
Encoding encoding = null)
153160
{
154161
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
155162
if (path == null) throw new ArgumentNullException(nameof(path));
@@ -165,7 +172,7 @@ public static LoggerConfiguration File(
165172
/// <param name="sinkConfiguration">Logger sink configuration.</param>
166173
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
167174
/// text for the file. If control of regular text formatting is required, use the other
168-
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool)"/>
175+
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding)"/>
169176
/// and specify the outputTemplate parameter instead.
170177
/// </param>
171178
/// <param name="path">Path to the file.</param>
@@ -183,6 +190,9 @@ public static LoggerConfiguration File(
183190
/// <param name="rollingInterval">The interval at which logging will roll over to a new file.</param>
184191
/// <param name="rollOnFileSizeLimit">If <code>true</code>, a new file will be created when the file size limit is reached. Filenames
185192
/// will have a number appended in the format <code>_NNNNN</code>, with the first filename given no number.</param>
193+
/// <param name="retainedFileCountLimit">The maximum number of log files that will be retained,
194+
/// including the current log file. For unlimited retention, pass null. The default is 31.</param>
195+
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
186196
/// <returns>Configuration object allowing method chaining.</returns>
187197
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
188198
public static LoggerConfiguration File(
@@ -196,9 +206,13 @@ public static LoggerConfiguration File(
196206
bool shared = false,
197207
TimeSpan? flushToDiskInterval = null,
198208
RollingInterval rollingInterval = RollingInterval.Infinite,
199-
bool rollOnFileSizeLimit = false)
209+
bool rollOnFileSizeLimit = false,
210+
int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
211+
Encoding encoding = null)
200212
{
201-
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval, rollingInterval: rollingInterval, rollOnFileSizeLimit: rollOnFileSizeLimit);
213+
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch,
214+
buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval, rollingInterval: rollingInterval,
215+
rollOnFileSizeLimit: rollOnFileSizeLimit, retainedFileCountLimit: retainedFileCountLimit, encoding: encoding);
202216
}
203217

204218
/// <summary>
@@ -212,7 +226,7 @@ public static LoggerConfiguration File(
212226
/// to be changed at runtime.</param>
213227
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
214228
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
215-
/// the default is "[{Timestamp:o} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}".</param>
229+
/// the default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".</param>
216230
/// <returns>Configuration object allowing method chaining.</returns>
217231
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
218232
public static LoggerConfiguration File(
@@ -268,36 +282,48 @@ static LoggerConfiguration ConfigureFile(
268282
bool propagateExceptions = false,
269283
bool shared = false,
270284
TimeSpan? flushToDiskInterval = null,
285+
Encoding encoding = null,
271286
RollingInterval rollingInterval = RollingInterval.Infinite,
272-
bool rollOnFileSizeLimit = false)
287+
bool rollOnFileSizeLimit = false,
288+
int? retainedFileCountLimit = DefaultRetainedFileCountLimit)
273289
{
274290
if (addSink == null) throw new ArgumentNullException(nameof(addSink));
275291
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
276292
if (path == null) throw new ArgumentNullException(nameof(path));
277-
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative");
278-
if (shared && buffered)
279-
throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered));
293+
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.", nameof(fileSizeLimitBytes));
294+
if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit));
295+
if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered));
280296

281297
ILogEventSink sink;
282-
try
298+
299+
if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite)
300+
{
301+
sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit);
302+
}
303+
else
283304
{
284-
if (shared)
305+
try
285306
{
286-
sink = new SharedFileSink(path, formatter, fileSizeLimitBytes);
307+
#pragma warning disable 618
308+
if (shared)
309+
{
310+
sink = new SharedFileSink(path, formatter, fileSizeLimitBytes);
311+
}
312+
else
313+
{
314+
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
315+
}
316+
#pragma warning restore 618
287317
}
288-
else
318+
catch (Exception ex)
289319
{
290-
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
291-
}
292-
}
293-
catch (Exception ex)
294-
{
295-
SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex);
320+
SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex);
296321

297-
if (propagateExceptions)
298-
throw;
322+
if (propagateExceptions)
323+
throw;
299324

300-
return addSink(new NullSink(), LevelAlias.Maximum, null);
325+
return addSink(new NullSink(), LevelAlias.Maximum, null);
326+
}
301327
}
302328

303329
if (flushToDiskInterval.HasValue)

src/Serilog.Sinks.File/Serilog.Sinks.File.csproj

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Description>Write Serilog events to a text file in plain or JSON format.</Description>
4+
<Description>Write Serilog events to text files in plain or JSON format.</Description>
55
<VersionPrefix>4.0.0</VersionPrefix>
66
<Authors>Serilog Contributors</Authors>
77
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
@@ -11,25 +11,30 @@
1111
<SignAssembly>true</SignAssembly>
1212
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
1313
<PackageId>Serilog.Sinks.File</PackageId>
14-
<PackageTags>serilog;file;io</PackageTags>
14+
<PackageTags>serilog;file</PackageTags>
1515
<PackageIconUrl>http://serilog.net/images/serilog-sink-nuget.png</PackageIconUrl>
1616
<PackageProjectUrl>http://serilog.net</PackageProjectUrl>
1717
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
1818
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
1919
<RootNamespace>Serilog</RootNamespace>
20+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
21+
<AssemblyName>Serilog.Sinks.File</AssemblyName>
22+
<!-- Don't reference the full NETStandard.Library -->
23+
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
2024
</PropertyGroup>
2125

2226
<ItemGroup>
23-
<PackageReference Include="Serilog" Version="2.3.0" />
27+
<PackageReference Include="Serilog" Version="2.5.0" />
2428
</ItemGroup>
2529

2630
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
2731
<Reference Include="System" />
32+
<Reference Include="System.Core" />
2833
<Reference Include="Microsoft.CSharp" />
2934
</ItemGroup>
3035

3136
<PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
32-
<DefineConstants>$(DefineConstants);ATOMIC_APPEND</DefineConstants>
37+
<DefineConstants>$(DefineConstants);ATOMIC_APPEND;HRESULTS</DefineConstants>
3338
</PropertyGroup>
3439

3540
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
@@ -43,6 +48,7 @@
4348
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.0.11" />
4449
<PackageReference Include="System.Threading.Timer" Version="4.0.1" />
4550
<PackageReference Include="System.Threading" Version="4.0.11" />
51+
<PackageReference Include="System.Runtime.InteropServices" Version="4.1.0" />
4652
</ItemGroup>
4753

4854
</Project>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
using System;
16+
17+
namespace Serilog.Sinks.File
18+
{
19+
static class Clock
20+
{
21+
static Func<DateTime> _dateTimeNow = () => DateTime.Now;
22+
23+
[ThreadStatic]
24+
static DateTime _testDateTimeNow;
25+
26+
public static DateTime DateTimeNow => _dateTimeNow();
27+
28+
// Time is set per thread to support parallel
29+
// If any thread uses the clock in test mode, all threads
30+
// must use it in test mode; once set to test mode only
31+
// terminating the application returns it to normal use.
32+
public static void SetTestDateTimeNow(DateTime now)
33+
{
34+
_testDateTimeNow = now;
35+
_dateTimeNow = () => _testDateTimeNow;
36+
}
37+
}
38+
}

src/Serilog.Sinks.File/Sinks/File/FileSink.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using System;
1616
using System.IO;
1717
using System.Text;
18-
using Serilog.Core;
1918
using Serilog.Events;
2019
using Serilog.Formatting;
2120

@@ -24,7 +23,8 @@ namespace Serilog.Sinks.File
2423
/// <summary>
2524
/// Write log events to a disk file.
2625
/// </summary>
27-
public sealed class FileSink : ILogEventSink, IFlushableFileSink, IDisposable
26+
[Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File()` instead.")]
27+
public sealed class FileSink : IFileSink, IDisposable
2828
{
2929
readonly TextWriter _output;
3030
readonly FileStream _underlyingStream;
@@ -50,7 +50,7 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
5050
{
5151
if (path == null) throw new ArgumentNullException(nameof(path));
5252
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
53-
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative");
53+
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.");
5454

5555
_textFormatter = textFormatter;
5656
_fileSizeLimitBytes = fileSizeLimitBytes;
@@ -71,27 +71,34 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
7171
_output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
7272
}
7373

74-
/// <summary>
75-
/// Emit the provided log event to the sink.
76-
/// </summary>
77-
/// <param name="logEvent">The log event to write.</param>
78-
public void Emit(LogEvent logEvent)
74+
bool IFileSink.EmitOrOverflow(LogEvent logEvent)
7975
{
8076
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
8177
lock (_syncRoot)
8278
{
8379
if (_fileSizeLimitBytes != null)
8480
{
8581
if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes.Value)
86-
return;
82+
return false;
8783
}
8884

8985
_textFormatter.Format(logEvent, _output);
9086
if (!_buffered)
9187
_output.Flush();
88+
89+
return true;
9290
}
9391
}
9492

93+
/// <summary>
94+
/// Emit the provided log event to the sink.
95+
/// </summary>
96+
/// <param name="logEvent">The log event to write.</param>
97+
public void Emit(LogEvent logEvent)
98+
{
99+
((IFileSink) this).EmitOrOverflow(logEvent);
100+
}
101+
95102
/// <inheritdoc />
96103
public void Dispose()
97104
{

0 commit comments

Comments
 (0)