diff --git a/Build.ps1 b/Build.ps1 index 07fcb2c..b9ed918 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,26 +1,60 @@ +Write-Output "build: Build started" + +& dotnet --info +& dotnet --list-sdks + Push-Location $PSScriptRoot -if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse } +if(Test-Path .\artifacts) { + Write-Output "build: Cleaning .\artifacts" + Remove-Item .\artifacts -Force -Recurse +} & dotnet restore --no-cache -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; -$suffix = @{ $true = ""; $false = "$branch-$revision"}[$branch -eq "main" -and $revision -ne "local"] +$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:APPVEYOR_REPO_BRANCH]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:APPVEYOR_BUILD_NUMBER]; +$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] +$commitHash = $(git rev-parse --short HEAD) +$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] + +Write-Output "build: Package version suffix is $suffix" +Write-Output "build: Build version suffix is $buildSuffix" -foreach ($src in ls src/Serilog.*) { +foreach ($src in Get-ChildItem src/*) { Push-Location $src - & dotnet pack -c Release -o ..\..\.\artifacts --version-suffix=$suffix --include-source - if($LASTEXITCODE -ne 0) { exit 1 } + Write-Output "build: Packaging project in $src" + + & dotnet build -c Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true + + if($suffix) { + & dotnet pack -c Release --no-build -o ..\..\artifacts --version-suffix=$suffix + } else { + & dotnet pack -c Release --no-build -o ..\..\artifacts + } + if($LASTEXITCODE -ne 0) { exit 1 } Pop-Location } -foreach ($test in ls test/Serilog.*.Tests) { +foreach ($test in Get-ChildItem test/*.Tests) { Push-Location $test + Write-Output "build: Testing project in $test" + & dotnet test -c Release + if($LASTEXITCODE -ne 0) { exit 3 } + + Pop-Location +} + +foreach ($test in Get-ChildItem test/*.PerformanceTests) { + Push-Location $test + + Write-Output "build: Building performance test project in $test" + + & dotnet build -c Release if($LASTEXITCODE -ne 0) { exit 2 } Pop-Location diff --git a/appveyor.yml b/appveyor.yml index 37b11c9..c04c69b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,15 +1,15 @@ version: '{build}' skip_tags: true -image: Visual Studio 2019 +image: Visual Studio 2022 build_script: -- ps: ./Build.ps1 +- pwsh: ./Build.ps1 test: off artifacts: - path: artifacts/Serilog.*.nupkg deploy: - provider: NuGet api_key: - secure: rbdBqxBpLt4MkB+mrDOYNDOd8aVZ1zMkysaVNAXNKnC41FYifzX3l9LM8DCrUWU5 + secure: oemq1E4zMR+LKQyrR83ZLcugPpZtl5OMKjtpMy/mbPEwuFGS+Oe46427D9KoHYD8 skip_symbols: true on: branch: /^(main|dev)$/ diff --git a/assets/Serilog.svg b/assets/Serilog.svg deleted file mode 100644 index 963d20a..0000000 --- a/assets/Serilog.svg +++ /dev/null @@ -1,189 +0,0 @@ - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..9bf45a3 Binary files /dev/null and b/assets/icon.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..ba75026 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.300", + "allowPrerelease": false, + "rollForward": "latestFeature" + } +} diff --git a/serilog-sinks-periodicbatching.sln b/serilog-sinks-periodicbatching.sln index 97f5600..f92a8b8 100644 --- a/serilog-sinks-periodicbatching.sln +++ b/serilog-sinks-periodicbatching.sln @@ -7,14 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5E1-DEB9-4A04-8BAB-24EC7240ADAF}" ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - appveyor.yml = appveyor.yml - Build.ps1 = Build.ps1 - CHANGES.md = CHANGES.md - README.md = README.md - RunPerfTests.ps1 = RunPerfTests.ps1 assets\Serilog.snk = assets\Serilog.snk + assets\icon.png = assets\icon.png EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F6E07A13-B9D3-4019-B25A-DE1F6C17E108}" @@ -25,6 +19,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.PeriodicBatch EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.PeriodicBatching.PerformanceTests", "test\Serilog.Sinks.PeriodicBatching.PerformanceTests\Serilog.Sinks.PeriodicBatching.PerformanceTests.csproj", "{80B760D1-3862-49AD-9D72-23608550C318}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sln", "sln", "{E49CF29C-7646-4E9E-82C6-BF81A76A116F}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + appveyor.yml = appveyor.yml + Build.ps1 = Build.ps1 + CHANGES.md = CHANGES.md + README.md = README.md + RunPerfTests.ps1 = RunPerfTests.ps1 + global.json = global.json + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/Serilog.Sinks.PeriodicBatching/Properties/AssemblyInfo.cs b/src/Serilog.Sinks.PeriodicBatching/Properties/AssemblyInfo.cs index e03a26b..ff8192e 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Properties/AssemblyInfo.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Reflection; using System.Runtime.CompilerServices; -[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyVersion("3.0.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Serilog.Sinks.PeriodicBatching/Serilog.Sinks.PeriodicBatching.csproj b/src/Serilog.Sinks.PeriodicBatching/Serilog.Sinks.PeriodicBatching.csproj index 601b6c0..8cbb235 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Serilog.Sinks.PeriodicBatching.csproj +++ b/src/Serilog.Sinks.PeriodicBatching/Serilog.Sinks.PeriodicBatching.csproj @@ -1,41 +1,53 @@ - + - The periodic batching sink for Serilog - 2.3.1 + Buffer batches of log events to be flushed asynchronously. + 3.0.0 Serilog Contributors net45;netstandard1.1;netstandard1.2;netstandard2.0;netstandard2.1 true - Serilog.Sinks.PeriodicBatching Serilog ../../assets/Serilog.snk true true - Serilog.Sinks.PeriodicBatching serilog;batching;timer - http://serilog.net/images/serilog-sink-nuget.png - http://serilog.net - http://www.apache.org/licenses/LICENSE-2.0 + icon.png + Apache-2.0 + https://github.com/serilog/serilog-sinks-periodicbatching https://github.com/serilog/serilog-sinks-periodicbatching git false + latest + True + false + enable + + + + $(DefineConstants);FEATURE_THREADING_TIMER + + + + $(DefineConstants);FEATURE_THREADING_TIMER;FEATURE_EXECUTION_CONTEXT + + + + $(DefineConstants);FEATURE_THREADING_TIMER;FEATURE_EXECUTION_CONTEXT;FEATURE_ASYNCDISPOSABLE - + + - - - $(DefineConstants);THREADING_TIMER - - - - $(DefineConstants);THREADING_TIMER;EXECUTION_CONTEXT - + + + + + diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BatchedConnectionStatus.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BatchedConnectionStatus.cs index 39d3769..5a3eda4 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BatchedConnectionStatus.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BatchedConnectionStatus.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs index 2cb02fc..23ca933 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/BoundedConcurrentQueue.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ class BoundedConcurrentQueue { const int Unbounded = -1; - readonly ConcurrentQueue _queue = new ConcurrentQueue(); + readonly ConcurrentQueue _queue = new(); readonly int _queueLimit; int _counter; diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/IBatchedLogEventSink.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/IBatchedLogEventSink.cs index 27357ea..e4672c9 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/IBatchedLogEventSink.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/IBatchedLogEventSink.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSink.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSink.cs index 78fefd7..30a338f 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSink.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSink.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,15 +18,13 @@ using Serilog.Core; using Serilog.Debugging; using Serilog.Events; -using System.Threading; -// ReSharper disable MemberCanBePrivate.Global, UnusedMember.Global, VirtualMemberNeverOverridden.Global, ClassWithVirtualMembersNeverInherited.Global +// ReSharper disable UnusedParameter.Global, ConvertIfStatementToConditionalTernaryExpression, MemberCanBePrivate.Global, UnusedMember.Global, VirtualMemberNeverOverridden.Global, ClassWithVirtualMembersNeverInherited.Global, SuspiciousTypeConversion.Global namespace Serilog.Sinks.PeriodicBatching { /// - /// Base class for sinks that log events in batches. Batching is - /// triggered asynchronously on a timer. + /// Buffers log events into batches for background flushing. /// /// /// To avoid unbounded memory growth, events are discarded after attempting @@ -34,22 +32,19 @@ namespace Serilog.Sinks.PeriodicBatching /// that want to change this behavior need to either implement from scratch, or /// embed retry logic in the batch emitting functions. /// - public class PeriodicBatchingSink : ILogEventSink, IDisposable, IBatchedLogEventSink + public sealed class PeriodicBatchingSink : ILogEventSink, IDisposable +#if FEATURE_ASYNCDISPOSABLE + , IAsyncDisposable +#endif { - /// - /// Constant used with legacy constructor to indicate that the internal queue shouldn't be limited. - /// - [Obsolete("Implement `IBatchedLogEventSink` and use the `PeriodicBatchingSinkOptions` constructor.")] - public const int NoQueueLimit = -1; - readonly IBatchedLogEventSink _batchedLogEventSink; readonly int _batchSizeLimit; readonly bool _eagerlyEmitFirstEvent; readonly BoundedConcurrentQueue _queue; readonly BatchedConnectionStatus _status; - readonly Queue _waitingBatch = new Queue(); + readonly Queue _waitingBatch = new(); - readonly object _stateLock = new object(); + readonly object _stateLock = new(); readonly PortableTimer _timer; @@ -64,57 +59,9 @@ public class PeriodicBatchingSink : ILogEventSink, IDisposable, IBatchedLogEvent /// it will dispose this object if possible. /// Options controlling behavior of the sink. public PeriodicBatchingSink(IBatchedLogEventSink batchedSink, PeriodicBatchingSinkOptions options) - : this(options) - { - _batchedLogEventSink = batchedSink ?? throw new ArgumentNullException(nameof(batchedSink)); - } - - /// - /// Construct a . New code should avoid subclassing - /// and use - /// - /// instead. - /// - /// The maximum number of events to include in a single batch. - /// The time to wait between checking for event batches. - [Obsolete("Implement `IBatchedLogEventSink` and use the `PeriodicBatchingSinkOptions` constructor.")] - protected PeriodicBatchingSink(int batchSizeLimit, TimeSpan period) - : this(new PeriodicBatchingSinkOptions - { - BatchSizeLimit = batchSizeLimit, - Period = period, - EagerlyEmitFirstEvent = true, - QueueLimit = null - }) - { - _batchedLogEventSink = this; - } - - /// - /// Construct a . New code should avoid subclassing - /// and use - /// - /// instead. - /// - /// The maximum number of events to include in a single batch. - /// The time to wait between checking for event batches. - /// Maximum number of events in the queue - use for an unbounded queue. - [Obsolete("Implement `IBatchedLogEventSink` and use the `PeriodicBatchingSinkOptions` constructor.")] - protected PeriodicBatchingSink(int batchSizeLimit, TimeSpan period, int queueLimit) - : this(new PeriodicBatchingSinkOptions - { - BatchSizeLimit = batchSizeLimit, - Period = period, - EagerlyEmitFirstEvent = true, - QueueLimit = queueLimit == NoQueueLimit ? (int?)null : queueLimit - }) - { - _batchedLogEventSink = this; - } - - PeriodicBatchingSink(PeriodicBatchingSinkOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); + _batchedLogEventSink = batchedSink ?? throw new ArgumentNullException(nameof(batchedSink)); if (options.BatchSizeLimit <= 0) throw new ArgumentOutOfRangeException(nameof(options), "The batch size limit must be greater than zero."); @@ -125,10 +72,11 @@ protected PeriodicBatchingSink(int batchSizeLimit, TimeSpan period, int queueLim _queue = new BoundedConcurrentQueue(options.QueueLimit); _status = new BatchedConnectionStatus(options.Period); _eagerlyEmitFirstEvent = options.EagerlyEmitFirstEvent; - _timer = new PortableTimer(cancel => OnTick()); + _timer = new PortableTimer(_ => OnTick()); } - void CloseAndFlush() + /// + public void Dispose() { lock (_stateLock) { @@ -141,70 +89,34 @@ void CloseAndFlush() _timer.Dispose(); // This is the place where SynchronizationContext.Current is unknown and can be != null - // so we prevent possible deadlocks here for sync-over-async downstream implementations - ResetSyncContextAndWait(OnTick); + // so we prevent possible deadlocks here for sync-over-async downstream implementations. + TaskUtil.ResetSyncContextAndWait(OnTick); - if (_batchedLogEventSink != this) - (_batchedLogEventSink as IDisposable)?.Dispose(); + (_batchedLogEventSink as IDisposable)?.Dispose(); } - - static void ResetSyncContextAndWait(Func taskFactory) + +#if FEATURE_ASYNCDISPOSABLE + /// + public async ValueTask DisposeAsync() { - var prevContext = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(null); - try - { - taskFactory().Wait(); - } - finally + lock (_stateLock) { - SynchronizationContext.SetSynchronizationContext(prevContext); - } - } + if (!_started || _unloading) + return; - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - Dispose(true); - } + _unloading = true; + } - /// - /// Free resources held by the sink. - /// - /// If true, called because the object is being disposed; if false, - /// the object is being disposed from the finalizer. - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; - CloseAndFlush(); - } + _timer.Dispose(); - /// - /// Emit a batch of log events, running to completion synchronously. - /// - /// The events to emit. - /// Override either or , - /// not both. - protected virtual void EmitBatch(IEnumerable events) - { - } + await OnTick().ConfigureAwait(false); - /// - /// Emit a batch of log events, running asynchronously. - /// - /// The events to emit. - /// Override either or , - /// not both. -#pragma warning disable 1998 - protected virtual async Task EmitBatchAsync(IEnumerable events) -#pragma warning restore 1998 - { - // ReSharper disable once MethodHasAsyncOverload - EmitBatch(events); + if (_batchedLogEventSink is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + else + (_batchedLogEventSink as IDisposable)?.Dispose(); } +#endif async Task OnTick() { @@ -216,17 +128,16 @@ async Task OnTick() while (_waitingBatch.Count < _batchSizeLimit && _queue.TryDequeue(out var next)) { - if (CanInclude(next)) - _waitingBatch.Enqueue(next); + _waitingBatch.Enqueue(next); } if (_waitingBatch.Count == 0) { - await _batchedLogEventSink.OnEmptyBatchAsync(); + await _batchedLogEventSink.OnEmptyBatchAsync().ConfigureAwait(false); return; } - await _batchedLogEventSink.EmitBatchAsync(_waitingBatch); + await _batchedLogEventSink.EmitBatchAsync(_waitingBatch).ConfigureAwait(false); batchWasFull = _waitingBatch.Count >= _batchSizeLimit; _waitingBatch.Clear(); @@ -308,44 +219,5 @@ public void Emit(LogEvent logEvent) _queue.TryEnqueue(logEvent); } - - /// - /// Determine whether a queued log event should be included in the batch. If - /// an override returns false, the event will be dropped. - /// - /// An event to test for inclusion. - /// True if the event should be included in the batch; otherwise, false. - // ReSharper disable once UnusedParameter.Global - protected virtual bool CanInclude(LogEvent logEvent) - { - return true; - } - - /// - /// Allows derived sinks to perform periodic work without requiring additional threads - /// or timers (thus avoiding additional flush/shut-down complexity). - /// - /// Override either or , - /// not both. - protected virtual void OnEmptyBatch() - { - } - - /// - /// Allows derived sinks to perform periodic work without requiring additional threads - /// or timers (thus avoiding additional flush/shut-down complexity). - /// - /// Override either or , - /// not both. -#pragma warning disable 1998 - protected virtual async Task OnEmptyBatchAsync() -#pragma warning restore 1998 - { - // ReSharper disable once MethodHasAsyncOverload - OnEmptyBatch(); - } - - Task IBatchedLogEventSink.EmitBatchAsync(IEnumerable batch) => EmitBatchAsync(batch); - Task IBatchedLogEventSink.OnEmptyBatchAsync() => OnEmptyBatchAsync(); } } diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSinkOptions.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSinkOptions.cs index 4bec61d..5acd457 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSinkOptions.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PeriodicBatchingSinkOptions.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PortableTimer.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PortableTimer.cs index 2752465..ac6e44d 100644 --- a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PortableTimer.cs +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/PortableTimer.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2020 Serilog Contributors +// Copyright © Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ namespace Serilog.Sinks.PeriodicBatching { class PortableTimer : IDisposable { - readonly object _stateLock = new object(); + readonly object _stateLock = new(); readonly Func _onTick; - readonly CancellationTokenSource _cancel = new CancellationTokenSource(); + readonly CancellationTokenSource _cancel = new(); -#if THREADING_TIMER +#if FEATURE_THREADING_TIMER readonly Timer _timer; #endif @@ -35,12 +35,10 @@ class PortableTimer : IDisposable public PortableTimer(Func onTick) { - if (onTick == null) throw new ArgumentNullException(nameof(onTick)); + _onTick = onTick ?? throw new ArgumentNullException(nameof(onTick)); - _onTick = onTick; - -#if THREADING_TIMER -#if EXECUTION_CONTEXT +#if FEATURE_THREADING_TIMER +#if FEATURE_EXECUTION_CONTEXT using (ExecutionContext.SuppressFlow()) #endif _timer = new Timer(_ => OnTick(), null, Timeout.Infinite, Timeout.Infinite); @@ -56,7 +54,7 @@ public void Start(TimeSpan interval) if (_disposed) throw new ObjectDisposedException(nameof(PortableTimer)); -#if THREADING_TIMER +#if FEATURE_THREADING_TIMER _timer.Change(interval, Timeout.InfiniteTimeSpan); #else Task.Delay(interval, _cancel.Token) @@ -131,7 +129,7 @@ public void Dispose() Monitor.Wait(_stateLock); } -#if THREADING_TIMER +#if FEATURE_THREADING_TIMER _timer.Dispose(); #endif @@ -139,4 +137,4 @@ public void Dispose() } } } -} \ No newline at end of file +} diff --git a/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/TaskUtil.cs b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/TaskUtil.cs new file mode 100644 index 0000000..f9a2aad --- /dev/null +++ b/src/Serilog.Sinks.PeriodicBatching/Sinks/PeriodicBatching/TaskUtil.cs @@ -0,0 +1,36 @@ +// Copyright © Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Serilog.Sinks.PeriodicBatching; + +static class TaskUtil +{ + public static void ResetSyncContextAndWait(Func taskFactory) + { + var prevContext = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(null); + try + { + taskFactory().Wait(); + } + finally + { + SynchronizationContext.SetSynchronizationContext(prevContext); + } + } +} \ No newline at end of file diff --git a/test/Serilog.Sinks.PeriodicBatching.Tests/PeriodicBatchingSinkTests.cs b/test/Serilog.Sinks.PeriodicBatching.Tests/PeriodicBatchingSinkTests.cs index a514e8f..33de006 100644 --- a/test/Serilog.Sinks.PeriodicBatching.Tests/PeriodicBatchingSinkTests.cs +++ b/test/Serilog.Sinks.PeriodicBatching.Tests/PeriodicBatchingSinkTests.cs @@ -1,84 +1,19 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Serilog.Sinks.PeriodicBatching.Tests.Support; using Xunit; -using Serilog.Events; using Serilog.Tests.Support; namespace Serilog.Sinks.PeriodicBatching.Tests { - class InMemoryBatchedSink : IBatchedLogEventSink, IDisposable - { - readonly TimeSpan _batchEmitDelay; - readonly object _stateLock = new object(); - bool _stopped; - - // Postmortem only - public bool WasCalledAfterDisposal { get; private set; } - public IList> Batches { get; } - public bool IsDisposed { get; private set; } - - public InMemoryBatchedSink(TimeSpan batchEmitDelay) - { - _batchEmitDelay = batchEmitDelay; - Batches = new List>(); - } - - public void Stop() - { - lock (_stateLock) - { - _stopped = true; - } - } - - public Task EmitBatchAsync(IEnumerable events) - { - lock (_stateLock) - { - if (_stopped) - return Task.FromResult(0); - - if (IsDisposed) - WasCalledAfterDisposal = true; - - Thread.Sleep(_batchEmitDelay); - Batches.Add(events.ToList()); - } - - return Task.FromResult(0); - } - - public Task OnEmptyBatchAsync() => Task.FromResult(0); - - public void Dispose() - { - lock (_stateLock) - IsDisposed = true; - } - } - - class NullBatchedSink : PeriodicBatchingSink - { - public NullBatchedSink(int batchSizeLimit, TimeSpan period, int queueLimit) -#pragma warning disable 618 - : base(batchSizeLimit, period, queueLimit) -#pragma warning restore 618 - { - } - } - public class PeriodicBatchingSinkTests { static readonly TimeSpan TinyWait = TimeSpan.FromMilliseconds(200); static readonly TimeSpan MicroWait = TimeSpan.FromMilliseconds(1); - // Some very, very approximate tests here :) - [Fact] - public void WhenAnEventIsEnqueuedItIsWrittenToABatch_OnFlush() + public void WhenAnEventIsEnqueuedItIsWrittenToABatchOnDispose() { var bs = new InMemoryBatchedSink(TimeSpan.Zero); var pbs = new PeriodicBatchingSink(bs, new PeriodicBatchingSinkOptions @@ -92,9 +27,28 @@ public void WhenAnEventIsEnqueuedItIsWrittenToABatch_OnFlush() Assert.True(bs.IsDisposed); Assert.False(bs.WasCalledAfterDisposal); } - + +#if FEATURE_ASYNCDISPOSABLE + [Fact] + public async ValueTask WhenAnEventIsEnqueuedItIsWrittenToABatchOnDisposeAsync() + { + var bs = new InMemoryBatchedSink(TimeSpan.Zero); + var pbs = new PeriodicBatchingSink(bs, new PeriodicBatchingSinkOptions + { BatchSizeLimit = 2, Period = TinyWait, EagerlyEmitFirstEvent = true }); + var evt = Some.InformationEvent(); + pbs.Emit(evt); + await pbs.DisposeAsync(); + Assert.Equal(1, bs.Batches.Count); + Assert.Equal(1, bs.Batches[0].Count); + Assert.Same(evt, bs.Batches[0][0]); + Assert.True(bs.IsDisposed); + Assert.True(bs.IsDisposedAsync); + Assert.False(bs.WasCalledAfterDisposal); + } +#endif + [Fact] - public void WhenAnEventIsEnqueuedItIsWrittenToABatch_OnTimer() + public void WhenAnEventIsEnqueuedItIsWrittenToABatchOnTimer() { var bs = new InMemoryBatchedSink(TimeSpan.Zero); var pbs = new PeriodicBatchingSink(bs, new PeriodicBatchingSinkOptions @@ -110,7 +64,7 @@ public void WhenAnEventIsEnqueuedItIsWrittenToABatch_OnTimer() } [Fact] - public void WhenAnEventIsEnqueuedItIsWrittenToABatch_FlushWhileRunning() + public void WhenAnEventIsEnqueuedItIsWrittenToABatchOnDisposeWhileRunning() { var bs = new InMemoryBatchedSink(TinyWait + TinyWait); var pbs = new PeriodicBatchingSink(bs, new PeriodicBatchingSinkOptions { BatchSizeLimit = 2, Period = MicroWait, EagerlyEmitFirstEvent = true }); @@ -122,13 +76,5 @@ public void WhenAnEventIsEnqueuedItIsWrittenToABatch_FlushWhileRunning() Assert.True(bs.IsDisposed); Assert.False(bs.WasCalledAfterDisposal); } - - [Fact] - public void SubclassesCanBeConstructedUsingNoQueueLimitConstant() - { -#pragma warning disable 618 - var _ = new NullBatchedSink(batchSizeLimit: 100, TimeSpan.FromSeconds(2), queueLimit: PeriodicBatchingSink.NoQueueLimit); -#pragma warning restore 618 - } } } diff --git a/test/Serilog.Sinks.PeriodicBatching.Tests/Serilog.Sinks.PeriodicBatching.Tests.csproj b/test/Serilog.Sinks.PeriodicBatching.Tests/Serilog.Sinks.PeriodicBatching.Tests.csproj index fa4a470..a258351 100644 --- a/test/Serilog.Sinks.PeriodicBatching.Tests/Serilog.Sinks.PeriodicBatching.Tests.csproj +++ b/test/Serilog.Sinks.PeriodicBatching.Tests/Serilog.Sinks.PeriodicBatching.Tests.csproj @@ -1,12 +1,16 @@  - net452;netcoreapp1.1 + net452;netcoreapp1.1;net6.0 ../../assets/Serilog.snk true true + + $(DefineConstants);FEATURE_ASYNCDISPOSABLE + + @@ -22,8 +26,4 @@ - - - - diff --git a/test/Serilog.Sinks.PeriodicBatching.Tests/Support/InMemoryBatchedSink.cs b/test/Serilog.Sinks.PeriodicBatching.Tests/Support/InMemoryBatchedSink.cs new file mode 100644 index 0000000..636f126 --- /dev/null +++ b/test/Serilog.Sinks.PeriodicBatching.Tests/Support/InMemoryBatchedSink.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Serilog.Events; + +namespace Serilog.Sinks.PeriodicBatching.Tests.Support +{ + sealed class InMemoryBatchedSink : IBatchedLogEventSink, IDisposable +#if FEATURE_ASYNCDISPOSABLE + , IAsyncDisposable +#endif + { + readonly TimeSpan _batchEmitDelay; + readonly object _stateLock = new object(); + bool _stopped; + + // Postmortem only + public bool WasCalledAfterDisposal { get; private set; } + public IList> Batches { get; } + public bool IsDisposed { get; private set; } + + public InMemoryBatchedSink(TimeSpan batchEmitDelay) + { + _batchEmitDelay = batchEmitDelay; + Batches = new List>(); + } + + public void Stop() + { + lock (_stateLock) + { + _stopped = true; + } + } + + public Task EmitBatchAsync(IEnumerable events) + { + lock (_stateLock) + { + if (_stopped) + return Task.FromResult(0); + + if (IsDisposed) + WasCalledAfterDisposal = true; + + Thread.Sleep(_batchEmitDelay); + Batches.Add(events.ToList()); + } + + return Task.FromResult(0); + } + + public Task OnEmptyBatchAsync() => Task.FromResult(0); + + public void Dispose() + { + lock (_stateLock) + IsDisposed = true; + } + +#if FEATURE_ASYNCDISPOSABLE + public bool IsDisposedAsync { get; private set; } + + public ValueTask DisposeAsync() + { + lock (_stateLock) + { + IsDisposedAsync = true; + Dispose(); + return default; + } + } +#endif + } +} \ No newline at end of file