Skip to content

Commit 8a648f6

Browse files
authored
Merge pull request #31 from serilog/dev
1.2.0 Release
2 parents 752c27f + 0c6b675 commit 8a648f6

26 files changed

+576
-283
lines changed

Bench.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ foreach ($test in ls test/*.PerformanceTests) {
99

1010
echo "bench: Benchmarking project in $test"
1111

12-
& dotnet test -c Release --framework net4.5.2
12+
& dotnet test -c Release --framework net46
1313
if($LASTEXITCODE -ne 0) { exit 3 }
1414

1515
Pop-Location

Build.ps1

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ echo "build: Version suffix is $suffix"
1818
foreach ($src in ls src/*) {
1919
Push-Location $src
2020

21-
echo "build: Packaging project in $src"
22-
23-
& dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix
21+
echo "build: Packaging project in $src"
22+
23+
if ($suffix) {
24+
& dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --include-source
25+
} else {
26+
& dotnet pack -c Release -o ..\..\artifacts --include-source
27+
}
28+
2429
if($LASTEXITCODE -ne 0) { exit 1 }
2530

2631
Pop-Location
@@ -29,7 +34,7 @@ foreach ($src in ls src/*) {
2934
foreach ($test in ls test/*.PerformanceTests) {
3035
Push-Location $test
3136

32-
echo "build: Building performance test project in $test"
37+
echo "build: Building performance test project in $test"
3338

3439
& dotnet build -c Release
3540
if($LASTEXITCODE -ne 0) { exit 2 }
@@ -40,7 +45,7 @@ foreach ($test in ls test/*.PerformanceTests) {
4045
foreach ($test in ls test/*.Tests) {
4146
Push-Location $test
4247

43-
echo "build: Testing project in $test"
48+
echo "build: Testing project in $test"
4449

4550
& dotnet test -c Release
4651
if($LASTEXITCODE -ne 0) { exit 3 }

README.md

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Serilog.Sinks.Async [![Build status](https://ci.appveyor.com/api/projects/status/gvk0wl7aows14spn?svg=true)](https://ci.appveyor.com/project/serilog/serilog-sinks-async) [![NuGet](https://img.shields.io/nuget/v/Serilog.Sinks.Async.svg)](https://www.nuget.org/packages/Serilog.Sinks.Async) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog)
22

3-
An asynchronous wrapper for other [Serilog](https://serilog.net) sinks. Use this sink to reduce the overhead of logging calls by delegating work to a background thread. This is especially suited to non-batching sinks like the [File](https://github.com/serilog/serilog-sinks-file) and [RollingFile](https://github.com/serilog-serilog-sinks-rollingfile) sinks that may be affected by I/O bottlenecks.
3+
An asynchronous wrapper for other [Serilog](https://serilog.net) sinks. Use this sink to reduce the overhead of logging calls by delegating work to a background thread. This is especially suited to non-batching sinks like the [File](https://github.com/serilog/serilog-sinks-file) and [RollingFile](https://github.com/serilog/serilog-sinks-rollingfile) sinks that may be affected by I/O bottlenecks.
44

5-
**Note:** many of the network-based sinks (_CouchDB_, _Elasticsearch_, _MongoDB_, _Seq_, _Splunk_...) already perform asychronous batching natively and do not benefit from this wrapper.
5+
**Note:** many of the network-based sinks (_CouchDB_, _Elasticsearch_, _MongoDB_, _Seq_, _Splunk_...) already perform asynchronous batching natively and do not benefit from this wrapper.
66

77
### Getting started
88

@@ -12,36 +12,94 @@ Install from [NuGet](https://nuget.org/packages/serilog.sinks.async):
1212
Install-Package Serilog.Sinks.Async
1313
```
1414

15-
Assuming you have already installed the target sink, such as the rolling file sink, move the wrapped sink's configuration within a `WriteTo.Async()` statement:
15+
Assuming you have already installed the target sink, such as the file sink, move the wrapped sink's configuration within a `WriteTo.Async()` statement:
1616

1717
```csharp
1818
Log.Logger = new LoggerConfiguration()
19-
.WriteTo.Async(a => a.RollingFile("logs/myapp-{Date}.txt"))
19+
.WriteTo.Async(a => a.File("logs/myapp.log"))
2020
// Other logger configuration
2121
.CreateLogger()
22-
22+
2323
Log.Information("This will be written to disk on the worker thread");
2424

25-
// At application shutdown
25+
// At application shutdown (results in monitors getting StopMonitoring calls)
2626
Log.CloseAndFlush();
2727
```
2828

29-
The wrapped sink (`RollingFile` in this case) will be invoked on a worker thread while your application's thread gets on with more important stuff.
29+
The wrapped sink (`File` in this case) will be invoked on a worker thread while your application's thread gets on with more important stuff.
3030

3131
Because the memory buffer may contain events that have not yet been written to the target sink, it is important to call `Log.CloseAndFlush()` or `Logger.Dispose()` when the application exits.
3232

33-
### Buffering
33+
### Buffering & Dropping
34+
35+
The default memory buffer feeding the worker thread is capped to 10,000 items, after which arriving events will be dropped. To increase or decrease this limit, specify it when configuring the async sink. One can determine whether events have been dropped via `Serilog.Async.IAsyncLogEventSinkInspector.DroppedMessagesCount` (see Sink State Inspection interface below).
36+
37+
```csharp
38+
// Reduce the buffer to 500 events
39+
.WriteTo.Async(a => a.File("logs/myapp.log"), bufferSize: 500)
40+
```
41+
42+
### Health Monitoring via the Monitor and Inspector interfaces
3443

35-
The default memory buffer feeding the worker thread is capped to 10,000 items, after which arriving events will be dropped. To increase or decrease this limit, specify it when configuring the async sink.
44+
The `Async` wrapper is primarily intended to allow one to achieve minimal logging latency at all times, even when writing to sinks that may momentarily block during the course of their processing (e.g., a `File` Sink might block for a low number of ms while flushing). The dropping behavior is an important failsafe; it avoids having an unbounded buffering behaviour should logging throughput overwhelm the sink, or the sink ingestion throughput degrade.
45+
46+
In practice, this configuration (assuming one provisions an adequate `bufferSize`) achieves an efficient and resilient logging configuration that can safely handle load without impacting processing throughput. The risk is of course that events get be dropped if the buffer threshold gets breached. The inspection interface, `IAsyncLogEventSinkInspector` (obtained by providing an `IAsyncLogEventSinkMonitor` when configuring the `Async` Sink), enables a health monitoring mechanism to actively validate that the buffer allocation is not being exceeded in practice.
3647

3748
```csharp
38-
// Reduce the buffer to 500 events
39-
.WriteTo.Async(a => a.RollingFile("logs/myapp-{Date}.txt"), 500)
49+
// Example check: log message to an out of band alarm channel if logging is showing signs of getting overwhelmed
50+
void ExecuteAsyncBufferCheck(IAsyncLogEventSinkInspector inspector)
51+
{
52+
var usagePct = inspector.Count * 100 / inspector.BoundedCapacity;
53+
if (usagePct > 50) SelfLog.WriteLine("Log buffer exceeded {0:p0} usage (limit: {1})", usagePct, inspector.BoundedCapacity);
54+
}
55+
56+
class MonitorConfiguration : IAsyncLogEventSinkMonitor
57+
{
58+
public void StartMonitoring(IAsyncLogEventSinkInspector inspector) =>
59+
HealthMonitor.AddPeriodicCheck(() => ExecuteAsyncBufferCheck(inspector));
60+
61+
public void StopMonitoring(IAsyncLogEventSinkInspector inspector)
62+
{ /* reverse of StartMonitoring */ }
63+
}
64+
65+
// Provide monitor so we can wire the health check to the inspector
66+
var monitor = new MonitorConfiguration();
67+
// Use default config (drop events if >10,000 backlog)
68+
.WriteTo.Async(a => a.File("logs/myapp.log"), monitor: monitor) ...
69+
```
70+
71+
### Blocking
72+
73+
Warning: For the same reason one typically does not want exceptions from logging to leak into the execution path, one typically does not want a logger to be able to have the side-efect of actually interrupting application processing until the log propagation has been unblocked.
74+
75+
When the buffer size limit is reached, the default behavior is to drop any further attempted writes until the queue abates, reporting each such failure to the `Serilog.Debugging.SelfLog`. To replace this with a blocking behaviour, set `blockWhenFull` to `true`.
76+
77+
```csharp
78+
// Wait for any queued event to be accepted by the `File` log before allowing the calling thread to resume its
79+
// application work after a logging call when there are 10,000 LogEvents awaiting ingestion by the pipeline
80+
.WriteTo.Async(a => a.File("logs/myapp.log"), blockWhenFull: true)
4081
```
4182

4283
### XML `<appSettings>` and JSON configuration
4384

44-
XML and JSON configuration support has not yet been added for this wrapper.
85+
Using [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) JSON:
86+
87+
```json
88+
{
89+
"Serilog": {
90+
"WriteTo": [{
91+
"Name": "Async",
92+
"Args": {
93+
"configure": [{
94+
"Name": "Console"
95+
}]
96+
}
97+
}]
98+
}
99+
}
100+
```
101+
102+
XML configuration support has not yet been added for this wrapper.
45103

46104
### About this sink
47105

appveyor.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
version: '{build}'
22
skip_tags: true
3-
image: Visual Studio 2015
3+
image: Visual Studio 2017
44
configuration: Release
5-
install:
6-
- ps: mkdir -Force ".\build\" | Out-Null
7-
- ps: Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain/dotnet-install.ps1" -OutFile ".\build\installcli.ps1"
8-
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli"
9-
- ps: '& .\build\installcli.ps1 -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath -Version 1.0.0-preview2-003121'
10-
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
115
build_script:
126
- ps: ./Build.ps1
137
test: off
@@ -16,7 +10,7 @@ artifacts:
1610
deploy:
1711
- provider: NuGet
1812
api_key:
19-
secure: nvZ/z+pMS91b3kG4DgfES5AcmwwGoBYQxr9kp4XiJHj25SAlgdIxFx++1N0lFH2x
13+
secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8
2014
skip_symbols: true
2115
on:
2216
branch: /^(master|dev)$/

global.json

Lines changed: 0 additions & 6 deletions
This file was deleted.

serilog-sinks-async.sln

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
21
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 14
4-
VisualStudioVersion = 14.0.25420.1
2+
# Visual Studio 15
3+
VisualStudioVersion = 15.0.26430.14
54
MinimumVisualStudioVersion = 10.0.40219.1
65
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{76C9F320-3DBC-4613-83AA-3CCD0D9012D9}"
76
EndProject
@@ -12,42 +11,41 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{154C7C
1211
appveyor.yml = appveyor.yml
1312
Bench.ps1 = Bench.ps1
1413
Build.ps1 = Build.ps1
15-
global.json = global.json
1614
LICENSE = LICENSE
1715
README.md = README.md
1816
EndProjectSection
1917
EndProject
20-
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Sinks.Async", "src\Serilog.Sinks.Async\Serilog.Sinks.Async.xproj", "{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}"
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Async", "src\Serilog.Sinks.Async\Serilog.Sinks.Async.csproj", "{003F6AB2-79F8-4A63-B501-5C564B4A4655}"
2119
EndProject
22-
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Sinks.Async.Tests", "test\Serilog.Sinks.Async.Tests\Serilog.Sinks.Async.Tests.xproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}"
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Async.PerformanceTests", "test\Serilog.Sinks.Async.PerformanceTests\Serilog.Sinks.Async.PerformanceTests.csproj", "{19E64565-3BE1-43FE-9E8B-7800C7061877}"
2321
EndProject
24-
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Sinks.Async.PerformanceTests", "test\Serilog.Sinks.Async.PerformanceTests\Serilog.Sinks.Async.PerformanceTests.xproj", "{D7A37F73-BBA3-4DAE-9648-1A753A86F968}"
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Async.Tests", "test\Serilog.Sinks.Async.Tests\Serilog.Sinks.Async.Tests.csproj", "{E8AE4DDD-6C28-4239-A752-075309A86D41}"
2523
EndProject
2624
Global
2725
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2826
Debug|Any CPU = Debug|Any CPU
2927
Release|Any CPU = Release|Any CPU
3028
EndGlobalSection
3129
GlobalSection(ProjectConfigurationPlatforms) = postSolution
32-
{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33-
{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
34-
{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
35-
{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Release|Any CPU.Build.0 = Release|Any CPU
36-
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37-
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU
38-
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU
39-
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU
40-
{D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41-
{D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.Build.0 = Debug|Any CPU
42-
{D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.ActiveCfg = Release|Any CPU
43-
{D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{003F6AB2-79F8-4A63-B501-5C564B4A4655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{003F6AB2-79F8-4A63-B501-5C564B4A4655}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{003F6AB2-79F8-4A63-B501-5C564B4A4655}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{003F6AB2-79F8-4A63-B501-5C564B4A4655}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{19E64565-3BE1-43FE-9E8B-7800C7061877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35+
{19E64565-3BE1-43FE-9E8B-7800C7061877}.Debug|Any CPU.Build.0 = Debug|Any CPU
36+
{19E64565-3BE1-43FE-9E8B-7800C7061877}.Release|Any CPU.ActiveCfg = Release|Any CPU
37+
{19E64565-3BE1-43FE-9E8B-7800C7061877}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{E8AE4DDD-6C28-4239-A752-075309A86D41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{E8AE4DDD-6C28-4239-A752-075309A86D41}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{E8AE4DDD-6C28-4239-A752-075309A86D41}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{E8AE4DDD-6C28-4239-A752-075309A86D41}.Release|Any CPU.Build.0 = Release|Any CPU
4442
EndGlobalSection
4543
GlobalSection(SolutionProperties) = preSolution
4644
HideSolutionNode = FALSE
4745
EndGlobalSection
4846
GlobalSection(NestedProjects) = preSolution
49-
{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C} = {76C9F320-3DBC-4613-83AA-3CCD0D9012D9}
50-
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {C36755AA-CED6-482B-B7B1-AE483BC3D273}
51-
{D7A37F73-BBA3-4DAE-9648-1A753A86F968} = {C36755AA-CED6-482B-B7B1-AE483BC3D273}
47+
{003F6AB2-79F8-4A63-B501-5C564B4A4655} = {76C9F320-3DBC-4613-83AA-3CCD0D9012D9}
48+
{19E64565-3BE1-43FE-9E8B-7800C7061877} = {C36755AA-CED6-482B-B7B1-AE483BC3D273}
49+
{E8AE4DDD-6C28-4239-A752-075309A86D41} = {C36755AA-CED6-482B-B7B1-AE483BC3D273}
5250
EndGlobalSection
5351
EndGlobal

src/Serilog.Sinks.Async/LoggerConfigurationAsyncExtensions.cs

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2+
using System.ComponentModel;
23
using Serilog.Configuration;
3-
using Serilog.Events;
44
using Serilog.Sinks.Async;
55

66
namespace Serilog
@@ -19,19 +19,59 @@ public static class LoggerConfigurationAsyncExtensions
1919
/// the thread is unable to process events quickly enough and the queue is filled, subsequent events will be
2020
/// dropped until room is made in the queue.</param>
2121
/// <returns>A <see cref="LoggerConfiguration"/> allowing configuration to continue.</returns>
22+
[EditorBrowsable(EditorBrowsableState.Never)]
2223
public static LoggerConfiguration Async(
2324
this LoggerSinkConfiguration loggerSinkConfiguration,
2425
Action<LoggerSinkConfiguration> configure,
25-
int bufferSize = 10000)
26+
int bufferSize)
2627
{
27-
var sublogger = new LoggerConfiguration();
28-
sublogger.MinimumLevel.Is(LevelAlias.Minimum);
29-
30-
configure(sublogger.WriteTo);
28+
return loggerSinkConfiguration.Async(configure, bufferSize, false);
29+
}
3130

32-
var wrapper = new BackgroundWorkerSink(sublogger.CreateLogger(), bufferSize);
31+
/// <summary>
32+
/// Configure a sink to be invoked asynchronously, on a background worker thread.
33+
/// </summary>
34+
/// <param name="loggerSinkConfiguration">The <see cref="LoggerSinkConfiguration"/> being configured.</param>
35+
/// <param name="configure">An action that configures the wrapped sink.</param>
36+
/// <param name="bufferSize">The size of the concurrent queue used to feed the background worker thread. If
37+
/// the thread is unable to process events quickly enough and the queue is filled, depending on
38+
/// <paramref name="blockWhenFull"/> the queue will block or subsequent events will be dropped until
39+
/// room is made in the queue.</param>
40+
/// <param name="blockWhenFull">Block when the queue is full, instead of dropping events.</param>
41+
/// <returns>A <see cref="LoggerConfiguration"/> allowing configuration to continue.</returns>
42+
public static LoggerConfiguration Async(
43+
this LoggerSinkConfiguration loggerSinkConfiguration,
44+
Action<LoggerSinkConfiguration> configure,
45+
int bufferSize = 10000,
46+
bool blockWhenFull = false)
47+
{
48+
return loggerSinkConfiguration.Async(configure, null, bufferSize, blockWhenFull);
49+
}
3350

34-
return loggerSinkConfiguration.Sink(wrapper);
51+
/// <summary>
52+
/// Configure a sink to be invoked asynchronously, on a background worker thread.
53+
/// Accepts a reference to a <paramref name="monitor"/> that will be supplied the internal state interface for health monitoring purposes.
54+
/// </summary>
55+
/// <param name="loggerSinkConfiguration">The <see cref="LoggerSinkConfiguration"/> being configured.</param>
56+
/// <param name="configure">An action that configures the wrapped sink.</param>
57+
/// <param name="bufferSize">The size of the concurrent queue used to feed the background worker thread. If
58+
/// the thread is unable to process events quickly enough and the queue is filled, depending on
59+
/// <paramref name="blockWhenFull"/> the queue will block or subsequent events will be dropped until
60+
/// room is made in the queue.</param>
61+
/// <param name="blockWhenFull">Block when the queue is full, instead of dropping events.</param>
62+
/// <param name="monitor">Monitor to supply buffer information to.</param>
63+
/// <returns>A <see cref="LoggerConfiguration"/> allowing configuration to continue.</returns>
64+
public static LoggerConfiguration Async(
65+
this LoggerSinkConfiguration loggerSinkConfiguration,
66+
Action<LoggerSinkConfiguration> configure,
67+
IAsyncLogEventSinkMonitor monitor,
68+
int bufferSize = 10000,
69+
bool blockWhenFull = false)
70+
{
71+
return LoggerSinkConfiguration.Wrap(
72+
loggerSinkConfiguration,
73+
wrappedSink => new BackgroundWorkerSink(wrappedSink, bufferSize, blockWhenFull, monitor),
74+
configure);
3575
}
3676
}
37-
}
77+
}

src/Serilog.Sinks.Async/Properties/AssemblyInfo.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Reflection;
33
using System.Runtime.CompilerServices;
44

5-
[assembly: AssemblyVersion("1.0.0.0")]
65
[assembly: CLSCompliant(true)]
76
[assembly: InternalsVisibleTo("Serilog.Sinks.Async.Tests, PublicKey=" +
87
"0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" +

0 commit comments

Comments
 (0)