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/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj b/src/Serilog.Sinks.Async/Serilog.Sinks.Async.csproj index 2301325..a073870 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.1 + 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