Skip to content

Commit

Permalink
Merge pull request #262 from agehrke/asyncdispose
Browse files Browse the repository at this point in the history
Async disposal on .NET 6 or later
  • Loading branch information
nblumhardt authored Nov 16, 2024
2 parents 5e1527d + ac351f2 commit 0584a5b
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Serilog.Core;
using Serilog.Events;
Expand All @@ -14,13 +15,19 @@ namespace Serilog.Extensions.Logging;
/// </summary>
[ProviderAlias("Serilog")]
public class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher, ISupportExternalScope
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable
#endif
{
internal const string OriginalFormatPropertyName = "{OriginalFormat}";
internal const string ScopePropertyName = "Scope";

// May be null; if it is, Log.Logger will be lazily used
readonly ILogger? _logger;
readonly Action? _dispose;
#if FEATURE_ASYNCDISPOSABLE
readonly Func<ValueTask>? _disposeAsync;
#endif
private IExternalScopeProvider? _externalScopeProvider;

/// <summary>
Expand All @@ -36,9 +43,25 @@ public SerilogLoggerProvider(ILogger? logger = null, bool dispose = false)
if (dispose)
{
if (logger != null)
{
_dispose = () => (logger as IDisposable)?.Dispose();
#if FEATURE_ASYNCDISPOSABLE
_disposeAsync = () =>
{
// Dispose via IAsyncDisposable if possible, otherwise fall back to IDisposable
if (logger is IAsyncDisposable asyncDisposable) return asyncDisposable.DisposeAsync();
else (logger as IDisposable)?.Dispose();
return default;
};
#endif
}
else
{
_dispose = Log.CloseAndFlush;
#if FEATURE_ASYNCDISPOSABLE
_disposeAsync = Log.CloseAndFlushAsync;
#endif
}
}
}

Expand Down Expand Up @@ -113,4 +136,12 @@ public void Dispose()
{
_dispose?.Invoke();
}

#if FEATURE_ASYNCDISPOSABLE
/// <inheritdoc />
public ValueTask DisposeAsync()
{
return _disposeAsync?.Invoke() ?? default;
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@
</ItemGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE</DefineConstants>
<DefineConstants>$(DefineConstants);FEATURE_ITUPLE;FEATURE_ASYNCDISPOSABLE</DefineConstants>
</PropertyGroup>
</Project>
72 changes: 72 additions & 0 deletions test/Serilog.Extensions.Logging.Tests/DisposeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Core;
using Serilog.Events;
using Xunit;

namespace Serilog.Extensions.Logging.Tests;

public class DisposeTests
{
private readonly DisposableSink _sink;
private readonly Logger _serilogLogger;

public DisposeTests()
{
_sink = new DisposableSink();
_serilogLogger = new LoggerConfiguration()
.WriteTo.Sink(_sink)
.CreateLogger();
}

[Fact]
public void DisposesProviderWhenDisposeIsTrue()
{
var services = new ServiceCollection()
.AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true))
.BuildServiceProvider();

// Get a logger so that we ensure SerilogLoggerProvider is created
var logger = services.GetRequiredService<ILogger<DisposeTests>>();
logger.LogInformation("Hello, world!");

services.Dispose();
Assert.True(_sink.DisposeCalled);
Assert.False(_sink.DisposeAsyncCalled);
}

#if NET8_0_OR_GREATER
[Fact]
public async Task DisposesProviderAsyncWhenDisposeIsTrue()
{
var services = new ServiceCollection()
.AddLogging(builder => builder.AddSerilog(logger: _serilogLogger, dispose: true))
.BuildServiceProvider();

// Get a logger so that we ensure SerilogLoggerProvider is created
var logger = services.GetRequiredService<ILogger<DisposeTests>>();
logger.LogInformation("Hello, world!");

await services.DisposeAsync();
Assert.False(_sink.DisposeCalled);
Assert.True(_sink.DisposeAsyncCalled);
}
#endif

private sealed class DisposableSink : ILogEventSink, IDisposable, IAsyncDisposable
{
public bool DisposeAsyncCalled { get; private set; }
public bool DisposeCalled { get; private set; }

public void Dispose() => DisposeCalled = true;
public ValueTask DisposeAsync()
{
DisposeAsyncCalled = true;
return default;
}

public void Emit(LogEvent logEvent)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Extensions.Logging.Tests.Support;
using Xunit;

namespace Serilog.Extensions.Logging.Tests;

public class SerilogLoggingBuilderExtensionsTests
{
[Fact]
public void AddSerilogMustRegisterAnILoggerProvider()
{
var services = new ServiceCollection()
.AddLogging(builder => { builder.AddSerilog(); })
.BuildServiceProvider();

var loggerProviders = services.GetServices<ILoggerProvider>();
Assert.Contains(loggerProviders, provider => provider is SerilogLoggerProvider);
}

[Fact]
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToStaticSerilogLogger()
{
var sink = new SerilogSink();
Log.Logger = new LoggerConfiguration()
.WriteTo.Sink(sink)
.CreateLogger();

var services = new ServiceCollection()
.AddLogging(builder => { builder.AddSerilog(); })
.BuildServiceProvider();

var logger = services.GetRequiredService<ILogger<SerilogLoggingBuilderExtensionsTests>>();
logger.LogInformation("Hello, world!");

Assert.Single(sink.Writes);
}

[Fact]
public void AddSerilogMustRegisterAnILoggerProviderThatForwardsLogsToProvidedLogger()
{
var sink = new SerilogSink();
var serilogLogger = new LoggerConfiguration()
.WriteTo.Sink(sink)
.CreateLogger();

var services = new ServiceCollection()
.AddLogging(builder => { builder.AddSerilog(logger: serilogLogger); })
.BuildServiceProvider();

var logger = services.GetRequiredService<ILogger<SerilogLoggingBuilderExtensionsTests>>();
logger.LogInformation("Hello, world!");

Assert.Single(sink.Writes);
}
}

0 comments on commit 0584a5b

Please sign in to comment.