Skip to content

Commit 7740c90

Browse files
authored
Initial support for IAsyncDisposable sinks (#1750)
* 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
1 parent 4d13be5 commit 7740c90

29 files changed

+510
-109
lines changed

src/Serilog/Capturing/PropertyValueConverter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ IEnumerable<LogEventPropertyValue> MapToSequenceElements(IEnumerable sequence, D
228228
return false;
229229
}
230230

231-
#if ITUPLE
231+
#if FEATURE_ITUPLE
232232

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

266266
// Ignore the 8+ value case for now.
267-
#if VALUETUPLE
267+
#if FEATURE_VALUETUPLE
268268
if (definition == typeof(ValueTuple<>) || definition == typeof(ValueTuple<,>) ||
269269
definition == typeof(ValueTuple<,,>) || definition == typeof(ValueTuple<,,,>) ||
270270
definition == typeof(ValueTuple<,,,,>) || definition == typeof(ValueTuple<,,,,,>) ||

src/Serilog/Configuration/LoggerSinkConfiguration.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public static LoggerConfiguration Wrap(
190190
Func<ILogEventSink, ILogEventSink> wrapSink,
191191
Action<LoggerSinkConfiguration> configureWrappedSink)
192192
{
193-
return Wrap(loggerSinkConfiguration, wrapSink, configureWrappedSink, LogEventLevel.Verbose, null);
193+
return Wrap(loggerSinkConfiguration, wrapSink, configureWrappedSink, LevelAlias.Minimum, null);
194194
}
195195

196196
/// <summary>
@@ -239,12 +239,20 @@ public static LoggerConfiguration Wrap(
239239
sinksToWrap.Single() :
240240
new DisposingAggregateSink(sinksToWrap);
241241

242-
var wrappedSink = wrapSink(enclosed);
243-
if (wrappedSink is not IDisposable && enclosed is IDisposable target)
242+
var wrapper = wrapSink(enclosed);
243+
if (wrapper is not IDisposable && enclosed is IDisposable
244+
#if FEATURE_ASYNCDISPOSABLE
245+
or IAsyncDisposable
246+
#endif
247+
)
244248
{
245-
wrappedSink = new DisposeDelegatingSink(wrappedSink, target);
249+
wrapper = new DisposeDelegatingSink(wrapper, enclosed as IDisposable
250+
#if FEATURE_ASYNCDISPOSABLE
251+
, enclosed as IAsyncDisposable
252+
#endif
253+
);
246254
}
247255

248-
return loggerSinkConfiguration.Sink(wrappedSink, restrictedToMinimumLevel, levelSwitch);
256+
return loggerSinkConfiguration.Sink(wrapper, restrictedToMinimumLevel, levelSwitch);
249257
}
250258
}

src/Serilog/Context/LogContext.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ namespace Serilog.Context;
3737
/// (and so is preserved across async/await calls).</remarks>
3838
public static class LogContext
3939
{
40-
#if ASYNCLOCAL
40+
#if FEATURE_ASYNCLOCAL
4141
static readonly AsyncLocal<EnricherStack?> Data = new();
42-
#elif REMOTING
42+
#elif FEATURE_REMOTING
4343
static readonly string DataSlotName = typeof(LogContext).FullName + "@" + Guid.NewGuid();
4444
#else // DOTNET_51
4545
[ThreadStatic]
@@ -199,15 +199,15 @@ public void Dispose()
199199
}
200200
}
201201

202-
#if ASYNCLOCAL
202+
#if FEATURE_ASYNCLOCAL
203203

204204
static EnricherStack? Enrichers
205205
{
206206
get => Data.Value;
207207
set => Data.Value = value;
208208
}
209209

210-
#elif REMOTING
210+
#elif FEATURE_REMOTING
211211

212212
static EnricherStack? Enrichers
213213
{

src/Serilog/Core/Logger.cs

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ namespace Serilog.Core;
2222
/// code should depend on <see cref="ILogger"/>, not this class.
2323
/// </summary>
2424
public sealed class Logger : ILogger, ILogEventSink, IDisposable
25+
#if FEATURE_ASYNCDISPOSABLE
26+
, IAsyncDisposable
27+
#endif
2528
{
2629
static readonly object[] NoPropertyValues = new object[0];
2730
static readonly LogEventProperty[] NoProperties = new LogEventProperty[0];
2831

2932
readonly MessageTemplateProcessor _messageTemplateProcessor;
3033
readonly ILogEventSink _sink;
3134
readonly Action? _dispose;
35+
#if FEATURE_ASYNCDISPOSABLE
36+
readonly Func<ValueTask>? _disposeAsync;
37+
#endif
3238
readonly ILogEventEnricher _enricher;
3339

3440
// It's important that checking minimum level is a very
@@ -43,40 +49,22 @@ public sealed class Logger : ILogger, ILogEventSink, IDisposable
4349
internal Logger(
4450
MessageTemplateProcessor messageTemplateProcessor,
4551
LogEventLevel minimumLevel,
52+
LoggingLevelSwitch? levelSwitch,
4653
ILogEventSink sink,
4754
ILogEventEnricher enricher,
48-
Action? dispose = null,
49-
LevelOverrideMap? overrideMap = null)
50-
: this(messageTemplateProcessor, minimumLevel, sink, enricher, dispose, null, overrideMap)
51-
{
52-
}
53-
54-
internal Logger(
55-
MessageTemplateProcessor messageTemplateProcessor,
56-
LoggingLevelSwitch levelSwitch,
57-
ILogEventSink sink,
58-
ILogEventEnricher enricher,
59-
Action? dispose = null,
60-
LevelOverrideMap? overrideMap = null)
61-
: this(messageTemplateProcessor, LevelAlias.Minimum, sink, enricher, dispose, levelSwitch, overrideMap)
62-
{
63-
}
64-
65-
// The messageTemplateProcessor, sink and enricher are required. Argument checks are dropped because
66-
// throwing from here breaks the logger's no-throw contract, and callers are all in this file anyway.
67-
Logger(
68-
MessageTemplateProcessor messageTemplateProcessor,
69-
LogEventLevel minimumLevel,
70-
ILogEventSink sink,
71-
ILogEventEnricher enricher,
72-
Action? dispose = null,
73-
LoggingLevelSwitch? levelSwitch = null,
74-
LevelOverrideMap? overrideMap = null)
55+
Action? dispose,
56+
#if FEATURE_ASYNCDISPOSABLE
57+
Func<ValueTask>? disposeAsync,
58+
#endif
59+
LevelOverrideMap? overrideMap)
7560
{
7661
_messageTemplateProcessor = messageTemplateProcessor;
7762
_minimumLevel = minimumLevel;
7863
_sink = sink;
7964
_dispose = dispose;
65+
#if FEATURE_ASYNCDISPOSABLE
66+
_disposeAsync = disposeAsync;
67+
#endif
8068
_levelSwitch = levelSwitch;
8169
_overrideMap = overrideMap;
8270
_enricher = enricher;
@@ -97,10 +85,13 @@ public ILogger ForContext(ILogEventEnricher enricher)
9785
return new Logger(
9886
_messageTemplateProcessor,
9987
_minimumLevel,
88+
_levelSwitch,
10089
this,
10190
enricher,
10291
null,
103-
_levelSwitch,
92+
#if FEATURE_ASYNCDISPOSABLE
93+
null,
94+
#endif
10495
_overrideMap);
10596
}
10697

@@ -149,10 +140,13 @@ public ILogger ForContext(string propertyName, object? value, bool destructureOb
149140
return new Logger(
150141
_messageTemplateProcessor,
151142
minimumLevel,
143+
levelSwitch,
152144
this,
153145
enricher,
154146
null,
155-
levelSwitch,
147+
#if FEATURE_ASYNCDISPOSABLE
148+
null,
149+
#endif
156150
_overrideMap);
157151
}
158152

@@ -1368,8 +1362,18 @@ public void Dispose()
13681362
_dispose?.Invoke();
13691363
}
13701364

1365+
#if FEATURE_ASYNCDISPOSABLE
1366+
/// <summary>
1367+
/// Close and flush the logging pipeline.
1368+
/// </summary>
1369+
public ValueTask DisposeAsync()
1370+
{
1371+
return _disposeAsync?.Invoke() ?? default;
1372+
}
1373+
#endif
1374+
13711375
/// <summary>
13721376
/// An <see cref="ILogger"/> instance that efficiently ignores all method calls.
13731377
/// </summary>
1374-
public static ILogger None { get; } = SilentLogger.Instance;
1378+
public static ILogger None { get; } = new SilentLogger();
13751379
}

src/Serilog/Core/Pipeline/MessageTemplateCache.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
#if HASHTABLE
15+
#if FEATURE_HASHTABLE
1616
#endif
1717

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

25-
#if HASHTABLE
25+
#if FEATURE_HASHTABLE
2626
readonly Hashtable _templates = new();
2727
#else
2828
readonly Dictionary<string, MessageTemplate> _templates = new();
@@ -43,7 +43,7 @@ public MessageTemplate Parse(string messageTemplate)
4343
if (messageTemplate.Length > MaxCachedTemplateLength)
4444
return _innerParser.Parse(messageTemplate);
4545

46-
#if HASHTABLE
46+
#if FEATURE_HASHTABLE
4747
// ReSharper disable once InconsistentlySynchronizedField
4848
// ignored warning because this is by design
4949
var result = (MessageTemplate?)_templates[messageTemplate];

src/Serilog/Core/Pipeline/SilentLogger.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,8 @@
1414

1515
namespace Serilog.Core.Pipeline;
1616

17-
class SilentLogger : ILogger
17+
sealed class SilentLogger : ILogger
1818
{
19-
public static readonly ILogger Instance = new SilentLogger();
20-
21-
SilentLogger()
22-
{
23-
}
24-
2519
public ILogger ForContext(ILogEventEnricher enricher) => this;
2620

2721
public ILogger ForContext(IEnumerable<ILogEventEnricher> enrichers) => this;

src/Serilog/Core/Sinks/ConditionalSink.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
namespace Serilog.Core.Sinks;
1616

17-
class ConditionalSink : ILogEventSink, IDisposable
17+
sealed class ConditionalSink : ILogEventSink, IDisposable
18+
#if FEATURE_ASYNCDISPOSABLE
19+
, IAsyncDisposable
20+
#endif
1821
{
1922
readonly ILogEventSink _wrapped;
2023
readonly Func<LogEvent, bool> _condition;
@@ -35,4 +38,15 @@ public void Dispose()
3538
{
3639
(_wrapped as IDisposable)?.Dispose();
3740
}
41+
42+
#if FEATURE_ASYNCDISPOSABLE
43+
public ValueTask DisposeAsync()
44+
{
45+
if (_wrapped is IAsyncDisposable asyncDisposable)
46+
return asyncDisposable.DisposeAsync();
47+
48+
Dispose();
49+
return default;
50+
}
51+
#endif
3852
}

src/Serilog/Core/Sinks/DisposeDelegatingSink.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,47 @@
1414

1515
namespace Serilog.Core.Sinks;
1616

17-
class DisposeDelegatingSink : ILogEventSink, IDisposable
17+
sealed class DisposeDelegatingSink : ILogEventSink, IDisposable
18+
#if FEATURE_ASYNCDISPOSABLE
19+
, IAsyncDisposable
20+
#endif
1821
{
1922
readonly ILogEventSink _sink;
20-
readonly IDisposable _disposable;
23+
readonly IDisposable? _disposable;
2124

22-
public DisposeDelegatingSink(ILogEventSink sink, IDisposable disposable)
25+
#if FEATURE_ASYNCDISPOSABLE
26+
readonly IAsyncDisposable? _asyncDisposable;
27+
#endif
28+
29+
public DisposeDelegatingSink(ILogEventSink sink, IDisposable? disposable
30+
#if FEATURE_ASYNCDISPOSABLE
31+
, IAsyncDisposable? asyncDisposable
32+
#endif
33+
)
2334
{
24-
_sink = Guard.AgainstNull(sink);
25-
_disposable = Guard.AgainstNull(disposable);
35+
_sink = sink;
36+
_disposable = disposable;
37+
38+
#if FEATURE_ASYNCDISPOSABLE
39+
_asyncDisposable = asyncDisposable;
40+
#endif
2641
}
2742

2843
public void Dispose()
2944
{
30-
_disposable.Dispose();
45+
_disposable?.Dispose();
46+
}
47+
48+
#if FEATURE_ASYNCDISPOSABLE
49+
public ValueTask DisposeAsync()
50+
{
51+
if (_asyncDisposable != null)
52+
return _asyncDisposable.DisposeAsync();
53+
54+
Dispose();
55+
return default;
3156
}
57+
#endif
3258

3359
public void Emit(LogEvent logEvent)
3460
{

src/Serilog/Core/Sinks/DisposingAggregateSink.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
namespace Serilog.Core.Sinks;
1616

17-
class DisposingAggregateSink : ILogEventSink, IDisposable
17+
sealed class DisposingAggregateSink : ILogEventSink, IDisposable
18+
#if FEATURE_ASYNCDISPOSABLE
19+
, IAsyncDisposable
20+
#endif
1821
{
1922
readonly ILogEventSink[] _sinks;
2023

@@ -57,8 +60,44 @@ public void Dispose()
5760
}
5861
catch (Exception ex)
5962
{
60-
SelfLog.WriteLine("Caught exception while disposing sink {0}: {1}", sink, ex);
63+
ReportDisposingException(sink, ex);
6164
}
6265
}
6366
}
67+
68+
#if FEATURE_ASYNCDISPOSABLE
69+
public async ValueTask DisposeAsync()
70+
{
71+
foreach (var sink in _sinks)
72+
{
73+
if (sink is IAsyncDisposable asyncDisposable)
74+
{
75+
try
76+
{
77+
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
78+
}
79+
catch (Exception ex)
80+
{
81+
ReportDisposingException(sink, ex);
82+
}
83+
}
84+
else if (sink is IDisposable disposable)
85+
{
86+
try
87+
{
88+
disposable.Dispose();
89+
}
90+
catch (Exception ex)
91+
{
92+
ReportDisposingException(sink, ex);
93+
}
94+
}
95+
}
96+
}
97+
#endif
98+
99+
static void ReportDisposingException(ILogEventSink sink, Exception ex)
100+
{
101+
SelfLog.WriteLine("Caught exception while disposing sink {0}: {1}", sink, ex);
102+
}
64103
}

0 commit comments

Comments
 (0)