From e2bbf9c326c73521962c0e964ff2441dc501daec Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Sun, 20 Oct 2024 08:29:09 +1000 Subject: [PATCH] Support ILoggingFailureListener --- .DS_Store | Bin 0 -> 6148 bytes .../Serilog.Sinks.Async.csproj | 4 +- .../Sinks/Async/BackgroundWorkerSink.cs | 22 +++++++-- .../BackgroundWorkerSinkTests.cs | 19 +++++++- .../Serilog.Sinks.Async.Tests.csproj | 1 + .../Support/CollectingFailureListener.cs | 46 ++++++++++++++++++ .../Support/NotImplementedSink.cs | 13 +++++ 7 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 .DS_Store create mode 100644 test/Serilog.Sinks.Async.Tests/Support/CollectingFailureListener.cs create mode 100644 test/Serilog.Sinks.Async.Tests/Support/NotImplementedSink.cs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ae3a3c941a62977b0d54a7d7f6bc156ec6c3f9c9 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8-BN@e6a+5|UMsdip~Xw6^#zRRL8T@%XfS3=liEWmd7qasAe+08QO0!_H&f8&)@!OAHVLKQn;)gM^0YTFedVtphr|KBK>ZhypskB@m@W*J5rE zJRsbp0-98ApBUVvgI(G<*J5taq%*EphI#DD#p8wR)xj=xIODEC>WKkjpv*ws3_E!K zU%+3c@{wOop%F1a4E!?&cw_91M^KbKTfdcuXRUa#TP-FI@s)fcwb4b{fA# a9pYSzxj~!-?K&NhE&`em>WG0~VBiZyAx!rG literal 0 HcmV?d00001 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