Repository: serilog/serilog

Issue:
CloseAndFlushAsync doesn't work with ReloadableLogger
**Description**
Calling Log.CloseAndFlushAsync() when using ReloadableLogger as a static logger silently fails and does nothing.

**Reproduction**
1. Set the static logger to a ReloadableLogger.
2. Call Log.CloseAndFlushAsync().

**Expected behavior**
The ReloadableLogger should either:

* Support DisposeAsync() and properly flush the sinks
* Fall back to Dispose() if DisposeAsync() is not available,
* Throw an exception indicating the failure to close and flush.

**Relevant package, tooling and runtime versions**
Serilog 4.0.1



Hints:
That's an awful bug, thanks for the report, will PR a patch now.

Patch:
diff --git a/src/Serilog/Log.cs b/src/Serilog/Log.cs
index 3258d852a..a5b1d8d67 100644
--- a/src/Serilog/Log.cs
+++ b/src/Serilog/Log.cs
@@ -59,11 +59,18 @@ public static void CloseAndFlush()
     /// <summary>
     /// Resets <see cref="Logger"/> to the default and disposes the original if possible
     /// </summary>
-    public static ValueTask CloseAndFlushAsync()
+    public static async ValueTask CloseAndFlushAsync()
     {
         var logger = Interlocked.Exchange(ref _logger, Serilog.Core.Logger.None);
 
-        return (logger as IAsyncDisposable)?.DisposeAsync() ?? default;
+        if (logger is IAsyncDisposable asyncDisposable)
+        {
+            await asyncDisposable.DisposeAsync();
+        }
+        else
+        {
+            (logger as IDisposable)?.Dispose();
+        }
     }
 #endif
 


Test patch:
diff --git a/test/Serilog.Tests/Core/BatchingSinkTests.cs b/test/Serilog.Tests/Core/BatchingSinkTests.cs
index 2f3efe7d6..7022cae1b 100644
--- a/test/Serilog.Tests/Core/BatchingSinkTests.cs
+++ b/test/Serilog.Tests/Core/BatchingSinkTests.cs
@@ -15,9 +15,9 @@ public void WhenAnEventIsEnqueuedItIsWrittenToABatchOnDispose()
         var evt = Some.InformationEvent();
         pbs.Emit(evt);
         pbs.Dispose();
-        Assert.Single(bs.Batches);
-        Assert.Single(bs.Batches[0]);
-        Assert.Same(evt, bs.Batches[0][0]);
+        var batch = Assert.Single(bs.Batches);
+        var actual = Assert.Single(batch);
+        Assert.Same(evt, actual);
         Assert.True(bs.IsDisposed);
         Assert.False(bs.WasCalledAfterDisposal);
     }
@@ -35,9 +35,9 @@ public async Task WhenAnEventIsEnqueuedItIsWrittenToABatchOnDisposeAsync()
             var evt = Some.InformationEvent();
             pbs.Emit(evt);
             await pbs.DisposeAsync();
-            Assert.Single(bs.Batches);
-            Assert.Single(bs.Batches[0]);
-            Assert.Same(evt, bs.Batches[0][0]);
+            var batch = Assert.Single(bs.Batches);
+            var actual = Assert.Single(batch);
+            Assert.Same(evt, actual);
             Assert.True(bs.IsDisposed);
             Assert.True(bs.IsDisposedAsync);
             Assert.False(bs.WasCalledAfterDisposal);
diff --git a/test/Serilog.Tests/LogTests.cs b/test/Serilog.Tests/LogTests.cs
index cbd9ceccb..dd9a51a39 100644
--- a/test/Serilog.Tests/LogTests.cs
+++ b/test/Serilog.Tests/LogTests.cs
@@ -39,6 +39,16 @@ public async Task CloseAndFlushAsyncDisposesTheLogger()
         Assert.True(disposableLogger.IsDisposedAsync);
     }
 
+    [Fact]
+    public async Task CloseAndFlushAsyncDisposesTheLoggerEvenWhenItIsNotAsyncDisposable()
+    {
+        var disposableLogger = new SyncDisposableLogger();
+        Assert.IsNotAssignableFrom<IAsyncDisposable>(disposableLogger);
+        Log.Logger = disposableLogger;
+        await Log.CloseAndFlushAsync();
+        Assert.True(disposableLogger.IsDisposed);
+    }
+
     [Fact]
     public async Task CloseAndFlushAsyncResetsLoggerToSilentLogger()
     {
diff --git a/test/Serilog.Tests/Support/DisposableLogger.cs b/test/Serilog.Tests/Support/DisposableLogger.cs
index 4c23b482b..7c85cc6f0 100644
--- a/test/Serilog.Tests/Support/DisposableLogger.cs
+++ b/test/Serilog.Tests/Support/DisposableLogger.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
 namespace Serilog.Tests.Support;
 
 public class DisposableLogger : ILogger, IDisposable
diff --git a/test/Serilog.Tests/Support/InMemoryBatchedSink.cs b/test/Serilog.Tests/Support/InMemoryBatchedSink.cs
index 0ae904f50..15b687150 100644
--- a/test/Serilog.Tests/Support/InMemoryBatchedSink.cs
+++ b/test/Serilog.Tests/Support/InMemoryBatchedSink.cs
@@ -6,18 +6,58 @@ sealed class InMemoryBatchedSink(TimeSpan batchEmitDelay) : IBatchedLogEventSink
 #endif
 {
     readonly object _stateLock = new();
-    bool _stopped;
+    readonly SyncState _syncState = new();
 
-    // Postmortem only
-    public bool WasCalledAfterDisposal { get; private set; }
-    public IList<IList<LogEvent>> Batches { get; } = new List<IList<LogEvent>>();
-    public bool IsDisposed { get; private set; }
+    sealed class SyncState
+    {
+        public bool Stopped { get; set; }
+        public bool WasCalledAfterDisposal { get; set; }
+        public List<IReadOnlyCollection<LogEvent>> Batches { get; } = new();
+        public bool IsDisposed { get; set; }
+        public bool IsDisposedAsync { get; set; }
+    }
+
+    public bool WasCalledAfterDisposal
+    {
+        get
+        {
+            lock (_stateLock)
+                return _syncState.WasCalledAfterDisposal;
+        }
+    }
+
+    public IReadOnlyList<IReadOnlyCollection<LogEvent>> Batches
+    {
+        get
+        {
+            lock (_stateLock)
+                return _syncState.Batches.ToList();
+        }
+    }
+
+    public bool IsDisposed
+    {
+        get
+        {
+            lock (_stateLock)
+                return _syncState.IsDisposed;
+        }
+    }
+
+    public bool IsDisposedAsync
+    {
+        get
+        {
+            lock (_stateLock)
+                return _syncState.IsDisposedAsync;
+        }
+    }
 
     public void Stop()
     {
         lock (_stateLock)
         {
-            _stopped = true;
+            _syncState.Stopped = true;
         }
     }
 
@@ -25,14 +65,14 @@ public Task EmitBatchAsync(IReadOnlyCollection<LogEvent> events)
     {
         lock (_stateLock)
         {
-            if (_stopped)
+            if (_syncState.Stopped)
                 return Task.CompletedTask;
 
             if (IsDisposed)
-                WasCalledAfterDisposal = true;
+                _syncState.WasCalledAfterDisposal = true;
 
             Thread.Sleep(batchEmitDelay);
-            Batches.Add(events.ToList());
+            _syncState.Batches.Add(events.ToList());
         }
 
         return Task.CompletedTask;
@@ -43,17 +83,15 @@ public Task EmitBatchAsync(IReadOnlyCollection<LogEvent> events)
     public void Dispose()
     {
         lock (_stateLock)
-            IsDisposed = true;
+            _syncState.IsDisposed = true;
     }
 
 #if FEATURE_ASYNCDISPOSABLE
-    public bool IsDisposedAsync { get; private set; }
-
     public ValueTask DisposeAsync()
     {
         lock (_stateLock)
         {
-            IsDisposedAsync = true;
+            _syncState.IsDisposedAsync = true;
             Dispose();
             return default;
         }
diff --git a/test/Serilog.Tests/Support/SyncDisposableLogger.cs b/test/Serilog.Tests/Support/SyncDisposableLogger.cs
new file mode 100644
index 000000000..8a6d88ed7
--- /dev/null
+++ b/test/Serilog.Tests/Support/SyncDisposableLogger.cs
@@ -0,0 +1,19 @@
+#if FEATURE_ASYNCDISPOSABLE && FEATURE_DEFAULT_INTERFACE
+
+namespace Serilog.Tests.Support;
+
+public class SyncDisposableLogger: ILogger, IDisposable
+{
+    public bool IsDisposed { get; private set; }
+
+    public void Write(LogEvent logEvent)
+    {
+    }
+
+    public void Dispose()
+    {
+        IsDisposed = true;
+    }
+}
+
+#endif