Skip to content

Commit

Permalink
Initial support for IAsyncDisposable sinks (#1750)
Browse files Browse the repository at this point in the history
* Support IAsyncDisposable.DisposeAsync() on Logger, and add Log.CloseAndFlushAsync()

* Add tests for Log.CloseAndFlushAsync()

* Seal DisposeAggregatingSink

* Fix tests

* Add missing ConfigureAwait(false)

* Implement IAsyncDisposable on remaining sink wrapper types

* More test coverage
  • Loading branch information
nblumhardt committed Sep 12, 2022
1 parent 4d13be5 commit 7740c90
Show file tree
Hide file tree
Showing 29 changed files with 510 additions and 109 deletions.
4 changes: 2 additions & 2 deletions src/Serilog/Capturing/PropertyValueConverter.cs
Expand Up @@ -228,7 +228,7 @@ IEnumerable<LogEventPropertyValue> MapToSequenceElements(IEnumerable sequence, D
return false;
}

#if ITUPLE
#if FEATURE_ITUPLE

bool TryConvertValueTuple(object value, Destructuring destructuring, [NotNullWhen(true)] out LogEventPropertyValue? result)
{
Expand Down Expand Up @@ -264,7 +264,7 @@ bool TryConvertValueTuple(object value, Destructuring destructuring, [NotNullWhe
var definition = valueType.GetGenericTypeDefinition();

// Ignore the 8+ value case for now.
#if VALUETUPLE
#if FEATURE_VALUETUPLE
if (definition == typeof(ValueTuple<>) || definition == typeof(ValueTuple<,>) ||
definition == typeof(ValueTuple<,,>) || definition == typeof(ValueTuple<,,,>) ||
definition == typeof(ValueTuple<,,,,>) || definition == typeof(ValueTuple<,,,,,>) ||
Expand Down
18 changes: 13 additions & 5 deletions src/Serilog/Configuration/LoggerSinkConfiguration.cs
Expand Up @@ -190,7 +190,7 @@ public LoggerConfiguration Conditional(Func<LogEvent, bool> condition, Action<Lo
Func<ILogEventSink, ILogEventSink> wrapSink,
Action<LoggerSinkConfiguration> configureWrappedSink)
{
return Wrap(loggerSinkConfiguration, wrapSink, configureWrappedSink, LogEventLevel.Verbose, null);
return Wrap(loggerSinkConfiguration, wrapSink, configureWrappedSink, LevelAlias.Minimum, null);
}

/// <summary>
Expand Down Expand Up @@ -239,12 +239,20 @@ public LoggerConfiguration Conditional(Func<LogEvent, bool> condition, Action<Lo
sinksToWrap.Single() :
new DisposingAggregateSink(sinksToWrap);

var wrappedSink = wrapSink(enclosed);
if (wrappedSink is not IDisposable && enclosed is IDisposable target)
var wrapper = wrapSink(enclosed);
if (wrapper is not IDisposable && enclosed is IDisposable
#if FEATURE_ASYNCDISPOSABLE
or IAsyncDisposable
#endif
)
{
wrappedSink = new DisposeDelegatingSink(wrappedSink, target);
wrapper = new DisposeDelegatingSink(wrapper, enclosed as IDisposable
#if FEATURE_ASYNCDISPOSABLE
, enclosed as IAsyncDisposable
#endif
);
}

return loggerSinkConfiguration.Sink(wrappedSink, restrictedToMinimumLevel, levelSwitch);
return loggerSinkConfiguration.Sink(wrapper, restrictedToMinimumLevel, levelSwitch);
}
}
8 changes: 4 additions & 4 deletions src/Serilog/Context/LogContext.cs
Expand Up @@ -37,9 +37,9 @@ namespace Serilog.Context;
/// (and so is preserved across async/await calls).</remarks>
public static class LogContext
{
#if ASYNCLOCAL
#if FEATURE_ASYNCLOCAL
static readonly AsyncLocal<EnricherStack?> Data = new();
#elif REMOTING
#elif FEATURE_REMOTING
static readonly string DataSlotName = typeof(LogContext).FullName + "@" + Guid.NewGuid();
#else // DOTNET_51
[ThreadStatic]
Expand Down Expand Up @@ -199,15 +199,15 @@ public void Dispose()
}
}

#if ASYNCLOCAL
#if FEATURE_ASYNCLOCAL

static EnricherStack? Enrichers
{
get => Data.Value;
set => Data.Value = value;
}

#elif REMOTING
#elif FEATURE_REMOTING

static EnricherStack? Enrichers
{
Expand Down
64 changes: 34 additions & 30 deletions src/Serilog/Core/Logger.cs
Expand Up @@ -22,13 +22,19 @@ namespace Serilog.Core;
/// code should depend on <see cref="ILogger"/>, not this class.
/// </summary>
public sealed class Logger : ILogger, ILogEventSink, IDisposable
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable
#endif
{
static readonly object[] NoPropertyValues = new object[0];
static readonly LogEventProperty[] NoProperties = new LogEventProperty[0];

readonly MessageTemplateProcessor _messageTemplateProcessor;
readonly ILogEventSink _sink;
readonly Action? _dispose;
#if FEATURE_ASYNCDISPOSABLE
readonly Func<ValueTask>? _disposeAsync;
#endif
readonly ILogEventEnricher _enricher;

// It's important that checking minimum level is a very
Expand All @@ -43,40 +49,22 @@ public sealed class Logger : ILogger, ILogEventSink, IDisposable
internal Logger(
MessageTemplateProcessor messageTemplateProcessor,
LogEventLevel minimumLevel,
LoggingLevelSwitch? levelSwitch,
ILogEventSink sink,
ILogEventEnricher enricher,
Action? dispose = null,
LevelOverrideMap? overrideMap = null)
: this(messageTemplateProcessor, minimumLevel, sink, enricher, dispose, null, overrideMap)
{
}

internal Logger(
MessageTemplateProcessor messageTemplateProcessor,
LoggingLevelSwitch levelSwitch,
ILogEventSink sink,
ILogEventEnricher enricher,
Action? dispose = null,
LevelOverrideMap? overrideMap = null)
: this(messageTemplateProcessor, LevelAlias.Minimum, sink, enricher, dispose, levelSwitch, overrideMap)
{
}

// The messageTemplateProcessor, sink and enricher are required. Argument checks are dropped because
// throwing from here breaks the logger's no-throw contract, and callers are all in this file anyway.
Logger(
MessageTemplateProcessor messageTemplateProcessor,
LogEventLevel minimumLevel,
ILogEventSink sink,
ILogEventEnricher enricher,
Action? dispose = null,
LoggingLevelSwitch? levelSwitch = null,
LevelOverrideMap? overrideMap = null)
Action? dispose,
#if FEATURE_ASYNCDISPOSABLE
Func<ValueTask>? disposeAsync,
#endif
LevelOverrideMap? overrideMap)
{
_messageTemplateProcessor = messageTemplateProcessor;
_minimumLevel = minimumLevel;
_sink = sink;
_dispose = dispose;
#if FEATURE_ASYNCDISPOSABLE
_disposeAsync = disposeAsync;
#endif
_levelSwitch = levelSwitch;
_overrideMap = overrideMap;
_enricher = enricher;
Expand All @@ -97,10 +85,13 @@ public ILogger ForContext(ILogEventEnricher enricher)
return new Logger(
_messageTemplateProcessor,
_minimumLevel,
_levelSwitch,
this,
enricher,
null,
_levelSwitch,
#if FEATURE_ASYNCDISPOSABLE
null,
#endif
_overrideMap);
}

Expand Down Expand Up @@ -149,10 +140,13 @@ public ILogger ForContext(string propertyName, object? value, bool destructureOb
return new Logger(
_messageTemplateProcessor,
minimumLevel,
levelSwitch,
this,
enricher,
null,
levelSwitch,
#if FEATURE_ASYNCDISPOSABLE
null,
#endif
_overrideMap);
}

Expand Down Expand Up @@ -1368,8 +1362,18 @@ public void Dispose()
_dispose?.Invoke();
}

#if FEATURE_ASYNCDISPOSABLE
/// <summary>
/// Close and flush the logging pipeline.
/// </summary>
public ValueTask DisposeAsync()
{
return _disposeAsync?.Invoke() ?? default;
}
#endif

/// <summary>
/// An <see cref="ILogger"/> instance that efficiently ignores all method calls.
/// </summary>
public static ILogger None { get; } = SilentLogger.Instance;
public static ILogger None { get; } = new SilentLogger();
}
6 changes: 3 additions & 3 deletions src/Serilog/Core/Pipeline/MessageTemplateCache.cs
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if HASHTABLE
#if FEATURE_HASHTABLE
#endif

namespace Serilog.Core.Pipeline;
Expand All @@ -22,7 +22,7 @@ class MessageTemplateCache : IMessageTemplateParser
readonly IMessageTemplateParser _innerParser;
readonly object _templatesLock = new();

#if HASHTABLE
#if FEATURE_HASHTABLE
readonly Hashtable _templates = new();
#else
readonly Dictionary<string, MessageTemplate> _templates = new();
Expand All @@ -43,7 +43,7 @@ public MessageTemplate Parse(string messageTemplate)
if (messageTemplate.Length > MaxCachedTemplateLength)
return _innerParser.Parse(messageTemplate);

#if HASHTABLE
#if FEATURE_HASHTABLE
// ReSharper disable once InconsistentlySynchronizedField
// ignored warning because this is by design
var result = (MessageTemplate?)_templates[messageTemplate];
Expand Down
8 changes: 1 addition & 7 deletions src/Serilog/Core/Pipeline/SilentLogger.cs
Expand Up @@ -14,14 +14,8 @@

namespace Serilog.Core.Pipeline;

class SilentLogger : ILogger
sealed class SilentLogger : ILogger
{
public static readonly ILogger Instance = new SilentLogger();

SilentLogger()
{
}

public ILogger ForContext(ILogEventEnricher enricher) => this;

public ILogger ForContext(IEnumerable<ILogEventEnricher> enrichers) => this;
Expand Down
16 changes: 15 additions & 1 deletion src/Serilog/Core/Sinks/ConditionalSink.cs
Expand Up @@ -14,7 +14,10 @@

namespace Serilog.Core.Sinks;

class ConditionalSink : ILogEventSink, IDisposable
sealed class ConditionalSink : ILogEventSink, IDisposable
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable
#endif
{
readonly ILogEventSink _wrapped;
readonly Func<LogEvent, bool> _condition;
Expand All @@ -35,4 +38,15 @@ public void Dispose()
{
(_wrapped as IDisposable)?.Dispose();
}

#if FEATURE_ASYNCDISPOSABLE
public ValueTask DisposeAsync()
{
if (_wrapped is IAsyncDisposable asyncDisposable)
return asyncDisposable.DisposeAsync();

Dispose();
return default;
}
#endif
}
38 changes: 32 additions & 6 deletions src/Serilog/Core/Sinks/DisposeDelegatingSink.cs
Expand Up @@ -14,21 +14,47 @@

namespace Serilog.Core.Sinks;

class DisposeDelegatingSink : ILogEventSink, IDisposable
sealed class DisposeDelegatingSink : ILogEventSink, IDisposable
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable
#endif
{
readonly ILogEventSink _sink;
readonly IDisposable _disposable;
readonly IDisposable? _disposable;

public DisposeDelegatingSink(ILogEventSink sink, IDisposable disposable)
#if FEATURE_ASYNCDISPOSABLE
readonly IAsyncDisposable? _asyncDisposable;
#endif

public DisposeDelegatingSink(ILogEventSink sink, IDisposable? disposable
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable? asyncDisposable
#endif
)
{
_sink = Guard.AgainstNull(sink);
_disposable = Guard.AgainstNull(disposable);
_sink = sink;
_disposable = disposable;

#if FEATURE_ASYNCDISPOSABLE
_asyncDisposable = asyncDisposable;
#endif
}

public void Dispose()
{
_disposable.Dispose();
_disposable?.Dispose();
}

#if FEATURE_ASYNCDISPOSABLE
public ValueTask DisposeAsync()
{
if (_asyncDisposable != null)
return _asyncDisposable.DisposeAsync();

Dispose();
return default;
}
#endif

public void Emit(LogEvent logEvent)
{
Expand Down
43 changes: 41 additions & 2 deletions src/Serilog/Core/Sinks/DisposingAggregateSink.cs
Expand Up @@ -14,7 +14,10 @@

namespace Serilog.Core.Sinks;

class DisposingAggregateSink : ILogEventSink, IDisposable
sealed class DisposingAggregateSink : ILogEventSink, IDisposable
#if FEATURE_ASYNCDISPOSABLE
, IAsyncDisposable
#endif
{
readonly ILogEventSink[] _sinks;

Expand Down Expand Up @@ -57,8 +60,44 @@ public void Dispose()
}
catch (Exception ex)
{
SelfLog.WriteLine("Caught exception while disposing sink {0}: {1}", sink, ex);
ReportDisposingException(sink, ex);
}
}
}

#if FEATURE_ASYNCDISPOSABLE
public async ValueTask DisposeAsync()
{
foreach (var sink in _sinks)
{
if (sink is IAsyncDisposable asyncDisposable)
{
try
{
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
ReportDisposingException(sink, ex);
}
}
else if (sink is IDisposable disposable)
{
try
{
disposable.Dispose();
}
catch (Exception ex)
{
ReportDisposingException(sink, ex);
}
}
}
}
#endif

static void ReportDisposingException(ILogEventSink sink, Exception ex)
{
SelfLog.WriteLine("Caught exception while disposing sink {0}: {1}", sink, ex);
}
}

0 comments on commit 7740c90

Please sign in to comment.