Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for IAsyncDisposable sinks #1750

Merged
merged 7 commits into from Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}
}