Skip to content

Commit 5336190

Browse files
committed
Merge branch 'EamonHetherton-dev' into dev
2 parents 61bd115 + 2fa2921 commit 5336190

21 files changed

+970
-149
lines changed

CHANGES.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
2.2.0
2+
- #9 - default to UTF-8 encoding without BOM
3+
14
2.1.0
25
- Support alternative `ITextFormatter`s through the configuration API (#4)
36

47
2.0.0
58
- Moved to new project
6-
- DotNet Core support
9+
- DotNet Core support

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ To avoid bringing down apps with runaway disk usage the file sink **limits file
1414
.WriteTo.File("log.txt", fileSizeLimitBytes: null)
1515
```
1616

17-
> **Important:** Only one process may write to a log file at a given time. For multi-process scenarios, either use separate files or one of the non-file-based sinks.
17+
> **Important:** By default only one process may use a log file at a given time. See _Shared log files_ below if multi-process logging is required.
1818
1919
### `<appSettings>` configuration
2020

2121
The sink can be configured in XML [app-settings format](https://github.com/serilog/serilog/wiki/AppSettings) if the _Serilog.Settings.AppSettings_ package is in use:
2222

2323
```xml
24+
<add key="serilog:using:File" value="Serilog.Sinks.File" />
2425
<add key="serilog:write-to:File.path" value="log.txt" />
2526
<add key="serilog:write-to:File.fileSizeLimitBytes" value="" />
2627
```
@@ -35,4 +36,18 @@ To emit JSON, rather than plain text, a formatter can be specified:
3536

3637
To configure an alternative formatter in XML `<appSettings>`, specify the formatter's assembly-qualified type name as the setting `value`.
3738

39+
### Shared log files
40+
41+
Multiple processes can concurrently write to the same log file if the `shared` parameter is set to `true`:
42+
43+
```csharp
44+
.WriteTo.File("log.txt", shared: true)
45+
```
46+
47+
### Performance
48+
49+
By default, the file sink will flush each event written through it to disk. To improve write performance, specifying `buffered: true` will permit the underlying stream to buffer writes.
50+
51+
The [Serilog.Sinks.Async](https://github.com/serilog/serilog-sinks-async) package can be used to wrap the file sink and perform all disk accss on a background worker thread.
52+
3853
_Copyright &copy; 2016 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html)._

example/Sample/Program.cs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.IO;
3+
using Serilog;
4+
using Serilog.Debugging;
5+
6+
namespace Sample
7+
{
8+
public class Program
9+
{
10+
public static void Main(string[] args)
11+
{
12+
SelfLog.Enable(Console.Out);
13+
14+
Log.Logger = new LoggerConfiguration()
15+
.WriteTo.File("log.txt")
16+
.CreateLogger();
17+
18+
var sw = System.Diagnostics.Stopwatch.StartNew();
19+
20+
for (var i = 0; i < 1000000; ++i)
21+
{
22+
Log.Information("Hello, file logger!");
23+
}
24+
25+
Log.CloseAndFlush();
26+
27+
sw.Stop();
28+
29+
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
30+
Console.WriteLine($"Size: {new FileInfo("log.txt").Length}");
31+
32+
Console.WriteLine("Press any key to delete the temporary log file...");
33+
Console.ReadKey(true);
34+
35+
File.Delete("log.txt");
36+
}
37+
}
38+
}

example/Sample/Sample.xproj

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
5+
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
6+
</PropertyGroup>
7+
8+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
9+
<PropertyGroup Label="Globals">
10+
<ProjectGuid>a34235a2-a717-4a1c-bf5c-f4a9e06e1260</ProjectGuid>
11+
<RootNamespace>Sample</RootNamespace>
12+
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
13+
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
14+
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
15+
</PropertyGroup>
16+
17+
<PropertyGroup>
18+
<SchemaVersion>2.0</SchemaVersion>
19+
</PropertyGroup>
20+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
21+
</Project>

example/Sample/project.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"buildOptions": {
3+
"emitEntryPoint": true
4+
},
5+
6+
"dependencies": {
7+
"Serilog.Sinks.File": { "target": "project" }
8+
},
9+
10+
"frameworks": {
11+
"netcoreapp1.0": {
12+
"imports": "dnxcore50",
13+
"dependencies": {
14+
"Microsoft.NETCore.App": {
15+
"type": "platform",
16+
"version": "1.0.0"
17+
}
18+
}
19+
},
20+
"net4.5": {}
21+
},
22+
"runtimes": { "win10-x64": {} }
23+
}

serilog-sinks-file.sln

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 14
4-
VisualStudioVersion = 14.0.25123.0
4+
VisualStudioVersion = 14.0.25420.1
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}"
77
EndProject
@@ -21,6 +21,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7B927378-9
2121
EndProject
2222
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Sinks.File.Tests", "test\Serilog.Sinks.File.Tests\Serilog.Sinks.File.Tests.xproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}"
2323
EndProject
24+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{196B1544-C617-4D7C-96D1-628713BDD52A}"
25+
EndProject
26+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Sample", "example\Sample\Sample.xproj", "{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}"
27+
EndProject
2428
Global
2529
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2630
Debug|Any CPU = Debug|Any CPU
@@ -35,12 +39,17 @@ Global
3539
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU
3640
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU
3741
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU
42+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.Build.0 = Release|Any CPU
3846
EndGlobalSection
3947
GlobalSection(SolutionProperties) = preSolution
4048
HideSolutionNode = FALSE
4149
EndGlobalSection
4250
GlobalSection(NestedProjects) = preSolution
4351
{57E0ED0E-0F45-48AB-A73D-6A92B7C32095} = {037440DE-440B-4129-9F7A-09B42D00397E}
4452
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {7B927378-9F16-4F6F-B3F6-156395136646}
53+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260} = {196B1544-C617-4D7C-96D1-628713BDD52A}
4554
EndGlobalSection
4655
EndGlobal

src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs

+124-16
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ public static class FileLoggerConfigurationExtensions
4242
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
4343
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
4444
/// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
45-
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
46-
/// For unrestricted growth, pass null. The default is 1 GB.</param>
45+
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
46+
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
47+
/// will be written in full even if it exceeds the limit.</param>
4748
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
4849
/// is false.</param>
50+
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
51+
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
4952
/// <returns>Configuration object allowing method chaining.</returns>
5053
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
5154
public static LoggerConfiguration File(
@@ -56,14 +59,16 @@ public static LoggerConfiguration File(
5659
IFormatProvider formatProvider = null,
5760
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
5861
LoggingLevelSwitch levelSwitch = null,
59-
bool buffered = false)
62+
bool buffered = false,
63+
bool shared = false,
64+
TimeSpan? flushToDiskInterval = null)
6065
{
6166
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
6267
if (path == null) throw new ArgumentNullException(nameof(path));
6368
if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate));
6469

6570
var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
66-
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered);
71+
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval);
6772
}
6873

6974
/// <summary>
@@ -72,18 +77,21 @@ public static LoggerConfiguration File(
7277
/// <param name="sinkConfiguration">Logger sink configuration.</param>
7378
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
7479
/// text for the file. If control of regular text formatting is required, use the other
75-
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool)"/>
80+
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?)"/>
7681
/// and specify the outputTemplate parameter instead.
7782
/// </param>
7883
/// <param name="path">Path to the file.</param>
7984
/// <param name="restrictedToMinimumLevel">The minimum level for
8085
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
8186
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
8287
/// to be changed at runtime.</param>
83-
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
84-
/// For unrestricted growth, pass null. The default is 1 GB.</param>
88+
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
89+
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
90+
/// will be written in full even if it exceeds the limit.</param>
8591
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
8692
/// is false.</param>
93+
/// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
94+
/// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
8795
/// <returns>Configuration object allowing method chaining.</returns>
8896
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
8997
public static LoggerConfiguration File(
@@ -93,28 +101,128 @@ public static LoggerConfiguration File(
93101
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
94102
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
95103
LoggingLevelSwitch levelSwitch = null,
96-
bool buffered = false)
104+
bool buffered = false,
105+
bool shared = false,
106+
TimeSpan? flushToDiskInterval = null)
107+
{
108+
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered: buffered, shared: shared, flushToDiskInterval: flushToDiskInterval);
109+
}
110+
111+
/// <summary>
112+
/// Write log events to the specified file.
113+
/// </summary>
114+
/// <param name="sinkConfiguration">Logger sink configuration.</param>
115+
/// <param name="path">Path to the file.</param>
116+
/// <param name="restrictedToMinimumLevel">The minimum level for
117+
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
118+
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
119+
/// to be changed at runtime.</param>
120+
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
121+
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
122+
/// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
123+
/// <returns>Configuration object allowing method chaining.</returns>
124+
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
125+
public static LoggerConfiguration File(
126+
this LoggerAuditSinkConfiguration sinkConfiguration,
127+
string path,
128+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
129+
string outputTemplate = DefaultOutputTemplate,
130+
IFormatProvider formatProvider = null,
131+
LoggingLevelSwitch levelSwitch = null)
97132
{
98133
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
134+
if (path == null) throw new ArgumentNullException(nameof(path));
135+
if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate));
136+
137+
var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
138+
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch);
139+
}
140+
141+
/// <summary>
142+
/// Write log events to the specified file.
143+
/// </summary>
144+
/// <param name="sinkConfiguration">Logger sink configuration.</param>
145+
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
146+
/// text for the file. If control of regular text formatting is required, use the other
147+
/// overload of <see cref="File(LoggerAuditSinkConfiguration, string, LogEventLevel, string, IFormatProvider, LoggingLevelSwitch)"/>
148+
/// and specify the outputTemplate parameter instead.
149+
/// </param>
150+
/// <param name="path">Path to the file.</param>
151+
/// <param name="restrictedToMinimumLevel">The minimum level for
152+
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
153+
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
154+
/// to be changed at runtime.</param>
155+
/// <returns>Configuration object allowing method chaining.</returns>
156+
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
157+
public static LoggerConfiguration File(
158+
this LoggerAuditSinkConfiguration sinkConfiguration,
159+
ITextFormatter formatter,
160+
string path,
161+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
162+
LoggingLevelSwitch levelSwitch = null)
163+
{
164+
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true);
165+
}
166+
167+
static LoggerConfiguration ConfigureFile(
168+
this Func<ILogEventSink, LogEventLevel, LoggingLevelSwitch, LoggerConfiguration> addSink,
169+
ITextFormatter formatter,
170+
string path,
171+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
172+
long? fileSizeLimitBytes = DefaultFileSizeLimitBytes,
173+
LoggingLevelSwitch levelSwitch = null,
174+
bool buffered = false,
175+
bool propagateExceptions = false,
176+
bool shared = false,
177+
TimeSpan? flushToDiskInterval = null)
178+
{
179+
if (addSink == null) throw new ArgumentNullException(nameof(addSink));
99180
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
100181
if (path == null) throw new ArgumentNullException(nameof(path));
182+
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative");
101183

102-
FileSink sink;
103-
try
184+
if (shared)
104185
{
105-
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
186+
#if ATOMIC_APPEND
187+
if (buffered)
188+
throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered));
189+
#else
190+
throw new NotSupportedException("File sharing is not supported on this platform.");
191+
#endif
106192
}
107-
catch (ArgumentException)
193+
194+
ILogEventSink sink;
195+
try
108196
{
109-
throw;
197+
#if ATOMIC_APPEND
198+
if (shared)
199+
{
200+
sink = new SharedFileSink(path, formatter, fileSizeLimitBytes);
201+
}
202+
else
203+
{
204+
#endif
205+
sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
206+
#if ATOMIC_APPEND
207+
}
208+
#endif
110209
}
111210
catch (Exception ex)
112211
{
113212
SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex);
114-
return sinkConfiguration.Sink(new NullSink());
213+
214+
if (propagateExceptions)
215+
throw;
216+
217+
return addSink(new NullSink(), LevelAlias.Maximum, null);
218+
}
219+
220+
if (flushToDiskInterval.HasValue)
221+
{
222+
sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value);
115223
}
116224

117-
return sinkConfiguration.Sink(sink, restrictedToMinimumLevel, levelSwitch);
225+
return addSink(sink, restrictedToMinimumLevel, levelSwitch);
118226
}
119227
}
120-
}
228+
}

0 commit comments

Comments
 (0)