diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ae3a3c9 Binary files /dev/null and b/.DS_Store differ diff --git a/Build.ps1 b/Build.ps1 index 3c41f46..6ded5be 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -29,7 +29,7 @@ foreach ($src in ls src/*) { } else { & dotnet pack -c Release -o ..\..\artifacts --no-build } - if($LASTEXITCODE -ne 0) { exit 1 } + if($LASTEXITCODE -ne 0) { throw "build failed" } Pop-Location } @@ -40,7 +40,7 @@ foreach ($test in ls test/*.Tests) { echo "build: Testing project in $test" & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } + if($LASTEXITCODE -ne 0) { throw "tests failed" } Pop-Location } diff --git a/README.md b/README.md index 6b09740..a023537 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 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) +# 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) 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. @@ -8,8 +8,8 @@ An asynchronous wrapper for other [Serilog](https://serilog.net) sinks. Use this Install from [NuGet](https://nuget.org/packages/serilog.sinks.async): -```powershell -Install-Package Serilog.Sinks.Async +```sh +dotnet add package Serilog.Sinks.Async ``` Assuming you have already installed the target sink, such as the file sink, move the wrapped sink's configuration within a `WriteTo.Async()` statement: diff --git a/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj b/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj index 6b2733f..db578f6 100644 --- a/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj +++ b/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj @@ -2,7 +2,7 @@ Asynchronous sink wrapper for Serilog. - 2.0.0 + 2.1.0 Jezz Santos;Serilog Contributors net471;net462 @@ -29,7 +29,7 @@ - + diff --git a/src/Serilog.Sinks.Async/Sinks/Async/BackgroundWorkerSink.cs b/src/Serilog.Sinks.Async/Sinks/Async/BackgroundWorkerSink.cs index e755cf3..f7c16af 100644 --- a/src/Serilog.Sinks.Async/Sinks/Async/BackgroundWorkerSink.cs +++ b/src/Serilog.Sinks.Async/Sinks/Async/BackgroundWorkerSink.cs @@ -22,13 +22,16 @@ namespace Serilog.Sinks.Async; -sealed class BackgroundWorkerSink : ILogEventSink, IAsyncLogEventSinkInspector, IDisposable +sealed class BackgroundWorkerSink : ILogEventSink, IAsyncLogEventSinkInspector, IDisposable, ISetLoggingFailureListener { readonly ILogEventSink _wrappedSink; readonly bool _blockWhenFull; readonly BlockingCollection _queue; readonly Task _worker; readonly IAsyncLogEventSinkMonitor? _monitor; + + // By contract, set only during initialization, so updates are not synchronized. + ILoggingFailureListener _failureListener = SelfLog.FailureListener; long _droppedMessages; @@ -46,7 +49,10 @@ public BackgroundWorkerSink(ILogEventSink wrappedSink, int bufferCapacity, bool public void Emit(LogEvent logEvent) { if (_queue.IsAddingCompleted) + { + _failureListener.OnLoggingFailed(this, LoggingFailureKind.Final, "the sink has been disposed", [logEvent], null); return; + } try { @@ -59,14 +65,15 @@ public void Emit(LogEvent logEvent) if (!_queue.TryAdd(logEvent)) { Interlocked.Increment(ref _droppedMessages); - SelfLog.WriteLine("{0} unable to enqueue, capacity {1}", typeof(BackgroundWorkerSink), _queue.BoundedCapacity); + _failureListener.OnLoggingFailed(this, LoggingFailureKind.Permanent, $"unable to enqueue, capacity {_queue.BoundedCapacity}", [logEvent], null); } } } - catch (InvalidOperationException) + catch (InvalidOperationException ex) { // Thrown in the event of a race condition when we try to add another event after // CompleteAdding has been called + _failureListener.OnLoggingFailed(this, LoggingFailureKind.Final, "the sink has been disposed", [logEvent], ex); } } @@ -95,13 +102,13 @@ void Pump() } catch (Exception ex) { - SelfLog.WriteLine("{0} failed to emit event to wrapped sink: {1}", typeof(BackgroundWorkerSink), ex); + _failureListener.OnLoggingFailed(this, LoggingFailureKind.Permanent, "failed to emit event to wrapped sink", [next], ex); } } } catch (Exception fatal) { - SelfLog.WriteLine("{0} fatal error in worker thread: {1}", typeof(BackgroundWorkerSink), fatal); + _failureListener.OnLoggingFailed(this, LoggingFailureKind.Final, "fatal error in worker thread", null, fatal); } } @@ -110,4 +117,9 @@ void Pump() int IAsyncLogEventSinkInspector.Count => _queue.Count; long IAsyncLogEventSinkInspector.DroppedMessagesCount => _droppedMessages; + + public void SetFailureListener(ILoggingFailureListener failureListener) + { + _failureListener = failureListener ?? throw new ArgumentNullException(nameof(failureListener)); + } } \ No newline at end of file diff --git a/test/Serilog.Sinks.Async.Tests/BackgroundWorkerSinkTests.cs b/test/Serilog.Sinks.Async.Tests/BackgroundWorkerSinkTests.cs index 50b6ce4..bd006c2 100644 --- a/test/Serilog.Sinks.Async.Tests/BackgroundWorkerSinkTests.cs +++ b/test/Serilog.Sinks.Async.Tests/BackgroundWorkerSinkTests.cs @@ -1,4 +1,6 @@ -using Serilog.Sinks.Async.Tests.Support; +using System; +using Serilog.Events; +using Serilog.Sinks.Async.Tests.Support; using Xunit; namespace Serilog.Sinks.Async.Tests; @@ -50,4 +52,19 @@ public void CtorAndDisposeInformMonitor() Assert.Null(monitor.Inspector); } + + [Fact] + public void SupportsLoggingFailureListener() + { + var failureListener = new CollectingFailureListener(); + var sink = new BackgroundWorkerSink(new NotImplementedSink(), 1, false, null); + sink.SetFailureListener(failureListener); + var evt = new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, MessageTemplate.Empty, []); + sink.Emit(evt); + sink.Dispose(); + var collected = Assert.Single(failureListener.Events); + Assert.Same(evt, collected); + var exception = Assert.Single(failureListener.Exceptions); + Assert.IsType(exception); + } } \ No newline at end of file diff --git a/test/Serilog.Sinks.Async.Tests/Serilog.Sinks.Async.Tests.csproj b/test/Serilog.Sinks.Async.Tests/Serilog.Sinks.Async.Tests.csproj index 644e6db..4dfe00a 100644 --- a/test/Serilog.Sinks.Async.Tests/Serilog.Sinks.Async.Tests.csproj +++ b/test/Serilog.Sinks.Async.Tests/Serilog.Sinks.Async.Tests.csproj @@ -6,6 +6,7 @@ ../../assets/Serilog.snk true true + 12 diff --git a/test/Serilog.Sinks.Async.Tests/Support/CollectingFailureListener.cs b/test/Serilog.Sinks.Async.Tests/Support/CollectingFailureListener.cs new file mode 100644 index 0000000..e35cc51 --- /dev/null +++ b/test/Serilog.Sinks.Async.Tests/Support/CollectingFailureListener.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Sinks.Async.Tests.Support; + +class CollectingFailureListener: ILoggingFailureListener +{ + readonly object _sync = new(); + readonly List _events = []; + readonly List _exceptions = []; + + public IReadOnlyList Events + { + get + { + lock (_sync) + return _events.ToList(); + } + } + public IReadOnlyList Exceptions + { + get + { + lock (_sync) + return _exceptions.ToList(); + } + } + + public void OnLoggingFailed(object sender, LoggingFailureKind kind, string message, IReadOnlyCollection events, + Exception exception) + { + lock (_sync) + { + if (exception != null) + _exceptions.Add(exception); + + foreach (var logEvent in events ?? []) + { + _events.Add(logEvent); + } + } + } +} \ No newline at end of file diff --git a/test/Serilog.Sinks.Async.Tests/Support/NotImplementedSink.cs b/test/Serilog.Sinks.Async.Tests/Support/NotImplementedSink.cs new file mode 100644 index 0000000..e0898c8 --- /dev/null +++ b/test/Serilog.Sinks.Async.Tests/Support/NotImplementedSink.cs @@ -0,0 +1,13 @@ +using System; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Sinks.Async.Tests.Support; + +class NotImplementedSink: ILogEventSink +{ + public void Emit(LogEvent logEvent) + { + throw new NotImplementedException(); + } +} \ No newline at end of file