Repository: serilog/serilog

Issue:
An ability to dispose nested loggers
**Is your feature request related to a problem? Please describe.**
I have a quite complex logging pipeline having at least two levels of nesting via `configuration.WriteTo.Logger(nestedLogger)`. The problem is, I can't dispose all of it in one go, because nested loggers are _not_ disposed when parent logger is.

**Describe the solution you'd like**
I would like to be able to specify that I want nested logger to be disposed with the pipeline, i.e.:
```
configuration.WriteTo.Logger(nestedLogger, dispose: true)
```
This can be easily accomplished by passing the flag down to `SecondaryLoggerSink`.

**Describe alternatives you've considered**
- `WriteTo.Logger(Action<LoggerConfiguration>)` which creates logger internally and disposes of it.
  I was able to replace only on of my usages with this one, because in other cases the logger is already created.
- `WriteTo.Sink(logger)` (using the fact that `Serilog.Core.Logger` is actually a `ILogEventSink`).
  This way the events pushed to logger are not copied so they can (and will be, in my case) modified by nested logger's enrichment. Also, feels hack-ish and requires access to `Logger` instead of `ILogger`.
- `WriteTo.Logger(lc => lc.WriteTo.Sink(logger))`.
  This, probably, will work (and I might settle for this in a short-term), but, same as above, requires access to `Logger` and feels even more hack-ish.

BTW, I'd add a simple overload in my own code, but both `SecondaryLoggerSink` and `LogEvent.Copy` are `internal`. 


Hints:
I think I can take this one myself, but I'd like to get an approval for the approach beforehand.
Thanks for the suggestion, will need to loop back around and give it some thought :+1:

I think the approach is sound; if anyone's interested in picking this up please jump in, otherwise given the lack of activity this should probably be closed as stale now.
I'll give it a try around the week end.

Patch:
diff --git a/src/Serilog/Configuration/LoggerSinkConfiguration.cs b/src/Serilog/Configuration/LoggerSinkConfiguration.cs
index ff6a71852..a1d043d60 100644
--- a/src/Serilog/Configuration/LoggerSinkConfiguration.cs
+++ b/src/Serilog/Configuration/LoggerSinkConfiguration.cs
@@ -142,8 +142,28 @@ public LoggerConfiguration Logger(
     /// events passed through the sink.</param>
     /// <returns>Configuration object allowing method chaining.</returns>
     /// <exception cref="ArgumentNullException">When <paramref name="logger"/> is <code>null</code></exception>
+    [EditorBrowsable(EditorBrowsableState.Never)]
+    public LoggerConfiguration Logger(
+        ILogger logger,
+        LogEventLevel restrictedToMinimumLevel)
+        => Logger(logger, attemptDispose: false, restrictedToMinimumLevel);
+
+    /// <summary>
+    /// Write log events to a sub-logger, where further processing may occur. Events through
+    /// the sub-logger will be constrained by filters and enriched by enrichers that are
+    /// active in the parent. A sub-logger cannot be used to log at a more verbose level, but
+    /// a less verbose level is possible.
+    /// </summary>
+    /// <param name="logger">The sub-logger.</param>
+    /// <param name="attemptDispose">Whether to shut down automatically the sub-logger
+    /// when the parent logger is disposed.</param>
+    /// <param name="restrictedToMinimumLevel">The minimum level for
+    /// events passed through the sink.</param>
+    /// <returns>Configuration object allowing method chaining.</returns>
+    /// <exception cref="ArgumentNullException">When <paramref name="logger"/> is <code>null</code></exception>
     public LoggerConfiguration Logger(
         ILogger logger,
+        bool attemptDispose = false,
         LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
     {
         Guard.AgainstNull(logger);
@@ -154,7 +174,7 @@ public LoggerConfiguration Logger(
                               "and may be removed completely in a future version.");
         }
 
-        var secondarySink = new SecondaryLoggerSink(logger, attemptDispose: false);
+        var secondarySink = new SecondaryLoggerSink(logger, attemptDispose: attemptDispose);
         return Sink(secondarySink, restrictedToMinimumLevel);
     }
 


Test patch:
diff --git a/test/Serilog.ApprovalTests/Serilog.approved.txt b/test/Serilog.ApprovalTests/Serilog.approved.txt
index 088733f46..7910898ec 100644
--- a/test/Serilog.ApprovalTests/Serilog.approved.txt
+++ b/test/Serilog.ApprovalTests/Serilog.approved.txt
@@ -68,7 +68,8 @@ namespace Serilog.Configuration
     public class LoggerSinkConfiguration
     {
         public Serilog.LoggerConfiguration Conditional(System.Func<Serilog.Events.LogEvent, bool> condition, System.Action<Serilog.Configuration.LoggerSinkConfiguration> configureSink) { }
-        public Serilog.LoggerConfiguration Logger(Serilog.ILogger logger, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0) { }
+        public Serilog.LoggerConfiguration Logger(Serilog.ILogger logger, Serilog.Events.LogEventLevel restrictedToMinimumLevel) { }
+        public Serilog.LoggerConfiguration Logger(Serilog.ILogger logger, bool attemptDispose = false, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0) { }
         public Serilog.LoggerConfiguration Logger(System.Action<Serilog.LoggerConfiguration> configureLogger, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { }
         public Serilog.LoggerConfiguration Sink(Serilog.Core.ILogEventSink logEventSink, Serilog.Events.LogEventLevel restrictedToMinimumLevel) { }
         public Serilog.LoggerConfiguration Sink(Serilog.Core.ILogEventSink logEventSink, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { }
diff --git a/test/Serilog.Tests/Core/ChildLoggerDisposalTests.cs b/test/Serilog.Tests/Core/ChildLoggerDisposalTests.cs
new file mode 100644
index 000000000..0d557e7c9
--- /dev/null
+++ b/test/Serilog.Tests/Core/ChildLoggerDisposalTests.cs
@@ -0,0 +1,76 @@
+﻿namespace Serilog.Tests.Core;
+
+public class ChildLoggerDisposalTests
+{
+    [Fact]
+    public void ByDefaultChildLoggerIsNotDisposedOnOuterDisposal()
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child)
+            .CreateLogger();
+        outer.Dispose();
+        Assert.False(child.IsDisposed);
+    }
+
+    [Fact]
+    public void ViaLegacyOverloadChildLoggerIsNotDisposedOnOuterDisposal()
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child, Information)
+            .CreateLogger();
+        outer.Dispose();
+        Assert.False(child.IsDisposed);
+    }
+
+    [Theory]
+    [InlineData(true)]
+    [InlineData(false)]
+    public void WhenExplicitlyConfiguredChildLoggerShouldBeDisposedAccordingToConfiguration(bool attemptDispose)
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child, attemptDispose)
+            .CreateLogger();
+        outer.Dispose();
+        Assert.Equal(attemptDispose, child.IsDisposed);
+    }
+
+#if FEATURE_ASYNCDISPOSABLE
+    [Fact]
+    public async Task ByDefaultChildLoggerIsNotAsyncDisposedOnOuterDisposal()
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child)
+            .CreateLogger();
+        await outer.DisposeAsync();
+        Assert.False(child.IsDisposedAsync);
+    }
+
+    [Fact]
+    public async Task ViaLegacyOverloadChildLoggerIsNotAsyncDisposedOnOuterDisposal()
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child, Information)
+            .CreateLogger();
+        await outer.DisposeAsync();
+        Assert.False(child.IsDisposedAsync);
+    }
+
+    [Theory]
+    [InlineData(true)]
+    [InlineData(false)]
+    public async Task WhenExplicitlyConfiguredChildLoggerShouldBeAsyncDisposedAccordingToConfiguration(bool attemptDispose)
+    {
+        var child = new DisposableLogger();
+        var outer = new LoggerConfiguration()
+            .WriteTo.Logger(child, attemptDispose)
+            .CreateLogger();
+        await outer.DisposeAsync();
+        Assert.Equal(attemptDispose, child.IsDisposedAsync);
+    }
+#endif
+}