Skip to content

Commit 0584a5b

Browse files
authored
Merge pull request #262 from agehrke/asyncdispose
Async disposal on .NET 6 or later
2 parents 5e1527d + ac351f2 commit 0584a5b

File tree

4 files changed

+163
-4
lines changed

4 files changed

+163
-4
lines changed

src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System.Threading.Tasks;
45
using Microsoft.Extensions.Logging;
56
using Serilog.Core;
67
using Serilog.Events;
@@ -14,13 +15,19 @@ namespace Serilog.Extensions.Logging;
1415
/// </summary>
1516
[ProviderAlias("Serilog")]
1617
public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher, ISupportExternalScope
18+
#if FEATURE_ASYNCDISPOSABLE
19+
, IAsyncDisposable
20+
#endif
1721
{
1822
internal const string OriginalFormatPropertyName = "{OriginalFormat}";
1923
internal const string ScopePropertyName = "Scope";
2024

2125
// May be null; if it is, Log.Logger will be lazily used
2226
readonly ILogger? _logger;
2327
readonly Action? _dispose;
28+
#if FEATURE_ASYNCDISPOSABLE
29+
readonly Func<ValueTask>? _disposeAsync;
30+
#endif
2431
private IExternalScopeProvider? _externalScopeProvider;
2532

2633
/// <summary>
@@ -36,9 +43,25 @@ public SerilogLoggerProvider(ILogger? logger = null, bool dispose = false)
3643
if (dispose)
3744
{
3845
if (logger != null)
46+
{
3947
_dispose = () => (logger as IDisposable)?.Dispose();
48+
#if FEATURE_ASYNCDISPOSABLE
49+
_disposeAsync = () =>
50+
{
51+
// Dispose via IAsyncDisposable if possible, otherwise fall back to IDisposable
52+
if (logger is IAsyncDisposable asyncDisposable) return asyncDisposable.DisposeAsync();
53+
else (logger as IDisposable)?.Dispose();
54+
return default;
55+
};
56+
#endif
57+
}
4058
else
59+
{
4160
_dispose = Log.CloseAndFlush;
61+
#if FEATURE_ASYNCDISPOSABLE
62+
_disposeAsync = Log.CloseAndFlushAsync;
63+
#endif
64+
}
4265
}
4366
}
4467

@@ -113,4 +136,12 @@ public void Dispose()
113136
{
114137
_dispose?.Invoke();
115138
}
139+
140+
#if FEATURE_ASYNCDISPOSABLE
141+
/// <inheritdoc />
142+
public ValueTask DisposeAsync()
143+
{
144+
return _disposeAsync?.Invoke() ?? default;
145+
}
146+
#endif
116147
}

src/Serilog.Extensions.Logging/Serilog.Extensions.Logging.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@
3636
</ItemGroup>
3737

3838
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
39-
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
39+
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
4040
</PropertyGroup>
4141

4242
<PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
43-
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
43+
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
4444
</PropertyGroup>
4545

4646
<PropertyGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
47-
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
47+
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
4848
</PropertyGroup>
4949

5050
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
51-
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
51+
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
5252
</PropertyGroup>
5353
</Project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
using Serilog.Core;
4+
using Serilog.Events;
5+
using Xunit;
6+
7+
namespace Serilog.Extensions.Logging.Tests;
8+
9+
public class DisposeTests
10+
{
11+
private readonly DisposableSink _sink;
12+
private readonly Logger _serilogLogger;
13+
14+
public DisposeTests()
15+
{
16+
_sink = new DisposableSink();
17+
_serilogLogger = new LoggerConfiguration()
18+
.WriteTo.Sink(_sink)
19+
.CreateLogger();
20+
}
21+
22+
[Fact]
23+
public void DisposesProviderWhenDisposeIsTrue()
24+
{
25+
var services = new ServiceCollection()
26+
.AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true))
27+
.BuildServiceProvider();
28+
29+
// Get a logger so that we ensure SerilogLoggerProvider is created
30+
var logger = services.GetRequiredService<ILogger<DisposeTests>>();
31+
logger.LogInformation("Hello, world!");
32+
33+
services.Dispose();
34+
Assert.True(_sink.DisposeCalled);
35+
Assert.False(_sink.DisposeAsyncCalled);
36+
}
37+
38+
#if NET8_0_OR_GREATER
39+
[Fact]
40+
public async Task DisposesProviderAsyncWhenDisposeIsTrue()
41+
{
42+
var services = new ServiceCollection()
43+
.AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true))
44+
.BuildServiceProvider();
45+
46+
// Get a logger so that we ensure SerilogLoggerProvider is created
47+
var logger = services.GetRequiredService<ILogger<DisposeTests>>();
48+
logger.LogInformation("Hello, world!");
49+
50+
await services.DisposeAsync();
51+
Assert.False(_sink.DisposeCalled);
52+
Assert.True(_sink.DisposeAsyncCalled);
53+
}
54+
#endif
55+
56+
private sealed class DisposableSink : ILogEventSink, IDisposable, IAsyncDisposable
57+
{
58+
public bool DisposeAsyncCalled { get; private set; }
59+
public bool DisposeCalled { get; private set; }
60+
61+
public void Dispose() => DisposeCalled = true;
62+
public ValueTask DisposeAsync()
63+
{
64+
DisposeAsyncCalled = true;
65+
return default;
66+
}
67+
68+
public void Emit(LogEvent logEvent)
69+
{
70+
}
71+
}
72+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
3+
using Serilog.Extensions.Logging.Tests.Support;
4+
using Xunit;
5+
6+
namespace Serilog.Extensions.Logging.Tests;
7+
8+
public class SerilogLoggingBuilderExtensionsTests
9+
{
10+
[Fact]
11+
public void AddSerilogMustRegisterAnILoggerProvider()
12+
{
13+
var services = new ServiceCollection()
14+
.AddLogging(builder => { builder.AddSerilog(); })
15+
.BuildServiceProvider();
16+
17+
var loggerProviders = services.GetServices<ILoggerProvider>();
18+
Assert.Contains(loggerProviders, provider => provider is SerilogLoggerProvider);
19+
}
20+
21+
[Fact]
22+
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToStaticSerilogLogger()
23+
{
24+
var sink = new SerilogSink();
25+
Log.Logger = new LoggerConfiguration()
26+
.WriteTo.Sink(sink)
27+
.CreateLogger();
28+
29+
var services = new ServiceCollection()
30+
.AddLogging(builder => { builder.AddSerilog(); })
31+
.BuildServiceProvider();
32+
33+
var logger = services.GetRequiredService<ILogger<SerilogLoggingBuilderExtensionsTests>>();
34+
logger.LogInformation("Hello, world!");
35+
36+
Assert.Single(sink.Writes);
37+
}
38+
39+
[Fact]
40+
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToProvidedLogger()
41+
{
42+
var sink = new SerilogSink();
43+
var serilogLogger = new LoggerConfiguration()
44+
.WriteTo.Sink(sink)
45+
.CreateLogger();
46+
47+
var services = new ServiceCollection()
48+
.AddLogging(builder => { builder.AddSerilog(logger: serilogLogger); })
49+
.BuildServiceProvider();
50+
51+
var logger = services.GetRequiredService<ILogger<SerilogLoggingBuilderExtensionsTests>>();
52+
logger.LogInformation("Hello, world!");
53+
54+
Assert.Single(sink.Writes);
55+
}
56+
}

0 commit comments

Comments
 (0)