diff --git a/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.MultipleStrategiesBenchmark-report-github.md b/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.MultipleStrategiesBenchmark-report-github.md index 761ed6b832..56839c5e0a 100644 --- a/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.MultipleStrategiesBenchmark-report-github.md +++ b/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.MultipleStrategiesBenchmark-report-github.md @@ -9,7 +9,8 @@ Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15 LaunchCount=2 WarmupCount=10 ``` -| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | -|--------------------------- |---------:|----------:|----------:|------:|-------:|----------:|------------:| -| ExecuteStrategyPipeline_V7 | 2.227 μs | 0.0077 μs | 0.0116 μs | 1.00 | 0.1106 | 2824 B | 1.00 | -| ExecuteStrategyPipeline_V8 | 1.750 μs | 0.0060 μs | 0.0084 μs | 0.79 | - | 40 B | 0.01 | +| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | +|------------------------------------- |---------:|----------:|----------:|------:|-------:|----------:|------------:| +| ExecuteStrategyPipeline_V7 | 2.269 μs | 0.0136 μs | 0.0204 μs | 1.00 | 0.1106 | 2824 B | 1.00 | +| ExecuteStrategyPipeline_V8 | 1.861 μs | 0.0111 μs | 0.0155 μs | 0.82 | - | 40 B | 0.01 | +| ExecuteStrategyPipeline_Telemetry_V8 | 2.402 μs | 0.0104 μs | 0.0156 μs | 1.06 | - | 40 B | 0.01 | diff --git a/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.TelemetryBenchmark-report-github.md b/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.TelemetryBenchmark-report-github.md index f16cd939b8..c3ab5b54ae 100644 --- a/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.TelemetryBenchmark-report-github.md +++ b/bench/BenchmarkDotNet.Artifacts/results/Polly.Core.Benchmarks.TelemetryBenchmark-report-github.md @@ -1,6 +1,6 @@ ``` ini -BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2), VM=Hyper-V +BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1848/22H2/2022Update/SunValley2), VM=Hyper-V Intel Xeon Platinum 8370C CPU 2.80GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=7.0.304 [Host] : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2 @@ -9,9 +9,9 @@ Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15 LaunchCount=2 WarmupCount=10 ``` -| Method | Telemetry | Enrichment | Mean | Error | StdDev | Gen0 | Allocated | -|-------- |---------- |----------- |------------:|---------:|----------:|-------:|----------:| -| **Execute** | **False** | **False** | **80.13 ns** | **0.324 ns** | **0.486 ns** | **-** | **-** | -| **Execute** | **False** | **True** | **74.33 ns** | **0.286 ns** | **0.392 ns** | **-** | **-** | -| **Execute** | **True** | **False** | **494.33 ns** | **3.973 ns** | **5.698 ns** | **0.0029** | **72 B** | -| **Execute** | **True** | **True** | **1,157.27 ns** | **7.130 ns** | **10.450 ns** | **0.0286** | **728 B** | +| Method | Telemetry | Enrichment | Mean | Error | StdDev | Allocated | +|-------- |---------- |----------- |------------:|---------:|---------:|----------:| +| **Execute** | **False** | **False** | **80.27 ns** | **1.992 ns** | **2.920 ns** | **-** | +| **Execute** | **False** | **True** | **79.68 ns** | **1.324 ns** | **1.982 ns** | **-** | +| **Execute** | **True** | **False** | **750.41 ns** | **4.875 ns** | **6.673 ns** | **-** | +| **Execute** | **True** | **True** | **1,034.73 ns** | **4.941 ns** | **7.242 ns** | **-** | diff --git a/bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs b/bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs index b7d1bca886..8fd1769a4f 100644 --- a/bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs +++ b/bench/Polly.Core.Benchmarks/MultipleStrategiesBenchmark.cs @@ -1,20 +1,32 @@ +using System.Diagnostics.Metrics; + namespace Polly.Core.Benchmarks; public class MultipleStrategiesBenchmark { + private MeterListener? _meterListener; private object? _strategyV7; private object? _strategyV8; + private object? _strategyTelemetryV8; [GlobalSetup] public void Setup() { - _strategyV7 = Helper.CreateStrategyPipeline(PollyVersion.V7); - _strategyV8 = Helper.CreateStrategyPipeline(PollyVersion.V8); + _meterListener = MeteringUtil.ListenPollyMetrics(); + _strategyV7 = Helper.CreateStrategyPipeline(PollyVersion.V7, false); + _strategyV8 = Helper.CreateStrategyPipeline(PollyVersion.V8, false); + _strategyTelemetryV8 = Helper.CreateStrategyPipeline(PollyVersion.V8, true); } + [GlobalCleanup] + public void Cleanup() => _meterListener?.Dispose(); + [Benchmark(Baseline = true)] public ValueTask ExecuteStrategyPipeline_V7() => _strategyV7!.ExecuteAsync(PollyVersion.V7); [Benchmark] public ValueTask ExecuteStrategyPipeline_V8() => _strategyV8!.ExecuteAsync(PollyVersion.V8); + + [Benchmark] + public ValueTask ExecuteStrategyPipeline_Telemetry_V8() => _strategyTelemetryV8!.ExecuteAsync(PollyVersion.V8); } diff --git a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs index 23143f6e3f..32f1d1fd19 100644 --- a/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs +++ b/bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Metrics; using Microsoft.Extensions.Logging.Abstractions; using Polly.Extensions.Telemetry; using Polly.Telemetry; @@ -7,13 +8,22 @@ namespace Polly.Core.Benchmarks; public class TelemetryBenchmark { private ResilienceStrategy? _strategy; + private MeterListener? _meterListener; [GlobalSetup] public void Prepare() { _strategy = Build(new ResilienceStrategyBuilder()); + + if (Telemetry) + { + _meterListener = MeteringUtil.ListenPollyMetrics(); + } } + [GlobalCleanup] + public void Cleanup() => _meterListener?.Dispose(); + [Params(true, false)] public bool Telemetry { get; set; } @@ -72,5 +82,4 @@ protected override ValueTask> ExecuteCoreAsync return callback(context, state); } } - } diff --git a/bench/Polly.Core.Benchmarks/Utils/Helper.MultipleStrategies.cs b/bench/Polly.Core.Benchmarks/Utils/Helper.MultipleStrategies.cs index 548b4e1cb8..8a549667e7 100644 --- a/bench/Polly.Core.Benchmarks/Utils/Helper.MultipleStrategies.cs +++ b/bench/Polly.Core.Benchmarks/Utils/Helper.MultipleStrategies.cs @@ -1,10 +1,11 @@ using System.Threading.RateLimiting; +using Microsoft.Extensions.Logging.Abstractions; namespace Polly.Core.Benchmarks.Utils; internal static partial class Helper { - public static object CreateStrategyPipeline(PollyVersion technology) => technology switch + public static object CreateStrategyPipeline(PollyVersion technology, bool telemetry) => technology switch { PollyVersion.V7 => Policy.WrapAsync( Policy.HandleResult(Failure).Or().AdvancedCircuitBreakerAsync(0.5, TimeSpan.FromSeconds(30), 10, TimeSpan.FromSeconds(5)), @@ -40,6 +41,11 @@ internal static partial class Helper _ => PredicateResult.False } }); + + if (telemetry) + { + builder.EnableTelemetry(NullLoggerFactory.Instance); + } }), _ => throw new NotSupportedException() }; diff --git a/bench/Polly.Core.Benchmarks/Utils/MeteringUtil.cs b/bench/Polly.Core.Benchmarks/Utils/MeteringUtil.cs new file mode 100644 index 0000000000..3a4245c6db --- /dev/null +++ b/bench/Polly.Core.Benchmarks/Utils/MeteringUtil.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace Polly.Core.Benchmarks.Utils; + +internal class MeteringUtil +{ + public static MeterListener ListenPollyMetrics() + { + var meterListener = new MeterListener + { + InstrumentPublished = (instrument, listener) => + { + if (instrument.Meter.Name is "Polly") + { + listener.EnableMeasurementEvents(instrument); + } + } + }; + + meterListener.SetMeasurementEventCallback(OnMeasurementRecorded); + meterListener.Start(); + + static void OnMeasurementRecorded( + Instrument instrument, + T measurement, + ReadOnlySpan> tags, + object? state) + { + // do nothing + } + + return meterListener; + } +} diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs index b3ae0d1b5b..ff3f7984ac 100644 --- a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs +++ b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs @@ -38,7 +38,11 @@ public void Report(string eventName, ResilienceContext context, TArgs arg return; } - DiagnosticSource.Write(eventName, new TelemetryEventArguments(TelemetrySource, eventName, context, null, args!)); + var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, eventName, context, null, args!); + + DiagnosticSource.Write(eventName, telemetryArgs); + + TelemetryEventArguments.Return(telemetryArgs); } /// @@ -60,7 +64,11 @@ public void Report(string eventName, OutcomeArguments Pool = new(() => new TelemetryEventArguments(), args => + { + args.Source = null!; + args.EventName = null!; + args.Context = null!; + args.Outcome = default; + args.Arguments = null!; + }); + + internal static TelemetryEventArguments Get( + ResilienceTelemetrySource source, + string eventName, + ResilienceContext context, + Outcome? outcome, + object arguments) + { + var args = Pool.Get(); + + args.Source = source; + args.EventName = eventName; + args.Context = context; + args.Outcome = outcome; + args.Arguments = arguments; + + return args; + } + + internal static void Return(TelemetryEventArguments args) => Pool.Return(args); +} diff --git a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs index 320c33aa6e..1c98f3145f 100644 --- a/src/Polly.Core/Telemetry/TelemetryEventArguments.cs +++ b/src/Polly.Core/Telemetry/TelemetryEventArguments.cs @@ -5,17 +5,35 @@ namespace Polly.Telemetry; /// /// The arguments of the telemetry event. /// -/// The source of the event. -/// The event name. -/// The resilience context. -/// The outcome of an execution. -/// The arguments associated with the event. [EditorBrowsable(EditorBrowsableState.Never)] -public sealed record class TelemetryEventArguments( - ResilienceTelemetrySource Source, - string EventName, - ResilienceContext Context, - Outcome? Outcome, - object Arguments) +public sealed partial record class TelemetryEventArguments { + private TelemetryEventArguments() + { + } + + /// + /// Gets the source of the event. + /// + public ResilienceTelemetrySource Source { get; private set; } = null!; + + /// + /// Gets the event name. + /// + public string EventName { get; private set; } = null!; + + /// + /// Gets the resilience context. + /// + public ResilienceContext Context { get; private set; } = null!; + + /// + /// Gets the outcome of an execution. + /// + public Outcome? Outcome { get; private set; } + + /// + /// Gets the arguments associated with the event. + /// + public object Arguments { get; private set; } = null!; } diff --git a/src/Polly.Core/Utils/ObjectPool.cs b/src/Polly.Core/Utils/ObjectPool.cs index b34ca97fa3..886e38a1dc 100644 --- a/src/Polly.Core/Utils/ObjectPool.cs +++ b/src/Polly.Core/Utils/ObjectPool.cs @@ -14,6 +14,11 @@ internal sealed class ObjectPool private T? _fastItem; private int _numItems; + public ObjectPool(Func createFunc, Action reset) + : this(createFunc, o => { reset(o); return true; }) + { + } + public ObjectPool(Func createFunc, Func returnFunc) : this(_ => createFunc(), returnFunc) { diff --git a/src/Polly.Extensions/Telemetry/EnrichmentContext.Pool.cs b/src/Polly.Extensions/Telemetry/EnrichmentContext.Pool.cs index 60224e48c8..fc03506a25 100644 --- a/src/Polly.Extensions/Telemetry/EnrichmentContext.Pool.cs +++ b/src/Polly.Extensions/Telemetry/EnrichmentContext.Pool.cs @@ -26,6 +26,7 @@ internal static EnrichmentContext Get(ResilienceContext resilienceContext, objec internal static void Return(EnrichmentContext context) { + Array.Clear(context._tagsArray, 0, context.Tags.Count); context.Tags.Clear(); ContextPool.Return(context); } diff --git a/src/Polly.Extensions/Telemetry/EnrichmentContext.cs b/src/Polly.Extensions/Telemetry/EnrichmentContext.cs index 51b54c2a11..6a0c8e5ba2 100644 --- a/src/Polly.Extensions/Telemetry/EnrichmentContext.cs +++ b/src/Polly.Extensions/Telemetry/EnrichmentContext.cs @@ -5,6 +5,10 @@ namespace Polly.Extensions.Telemetry; /// public sealed partial class EnrichmentContext { + private const int InitialArraySize = 20; + + private KeyValuePair[] _tagsArray = new KeyValuePair[InitialArraySize]; + private EnrichmentContext() { } @@ -27,5 +31,24 @@ private EnrichmentContext() /// /// Gets the tags associated with the resilience event. /// - public ICollection> Tags { get; } = new List>(); + public IList> Tags { get; } = new List>(); + + internal ReadOnlySpan> TagsSpan + { + get + { + // stryker disable once equality : no means to test this + if (Tags.Count > _tagsArray.Length) + { + Array.Resize(ref _tagsArray, Tags.Count); + } + + for (int i = 0; i < Tags.Count; i++) + { + _tagsArray[i] = Tags[i]; + } + + return _tagsArray.AsSpan(0, Tags.Count); + } + } } diff --git a/src/Polly.Extensions/Telemetry/EnrichmentUtil.cs b/src/Polly.Extensions/Telemetry/EnrichmentUtil.cs index 5575337cf1..b63606d53a 100644 --- a/src/Polly.Extensions/Telemetry/EnrichmentUtil.cs +++ b/src/Polly.Extensions/Telemetry/EnrichmentUtil.cs @@ -1,31 +1,19 @@ +using System.Collections.Generic; + namespace Polly.Extensions.Telemetry; internal static class EnrichmentUtil { - public static void Enrich( - ref TagList tags, - List> enrichers, - ResilienceContext resilienceContext, - Outcome? outcome, - object? resilienceArguments) + public static void Enrich(EnrichmentContext context, List> enrichers) { if (enrichers.Count == 0) { return; } - var context = EnrichmentContext.Get(resilienceContext, resilienceArguments, outcome); - foreach (var enricher in enrichers) { enricher(context); } - - foreach (var pair in context.Tags) - { - tags.Add(pair.Key, pair.Value); - } - - EnrichmentContext.Return(context); } } diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs index 55e637fa36..0f4a58bb38 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs @@ -41,20 +41,26 @@ public override void Write(string name, object? value) private void MeterEvent(TelemetryEventArguments args) { - var source = args.Source; - var tags = new TagList + if (!Counter.Enabled) { - { ResilienceTelemetryTags.EventName, args.EventName }, - { ResilienceTelemetryTags.BuilderName, source.BuilderName }, - { ResilienceTelemetryTags.StrategyName, source.StrategyName }, - { ResilienceTelemetryTags.StrategyType, source.StrategyType }, - { ResilienceTelemetryTags.StrategyKey, source.BuilderProperties.GetValue(TelemetryUtil.StrategyKey, null!) }, - { ResilienceTelemetryTags.ResultType, args.Context.GetResultType() }, - { ResilienceTelemetryTags.ExceptionName, args.Outcome?.Exception?.GetType().FullName } - }; - - EnrichmentUtil.Enrich(ref tags, _enrichers, args.Context, args.Outcome, args.Arguments); - Counter.Add(1, tags); + return; + } + + var source = args.Source; + + var enrichmentContext = EnrichmentContext.Get(args.Context, args.Arguments, args.Outcome); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.EventName, args.EventName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.BuilderName, source.BuilderName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyName, source.StrategyName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyType, source.StrategyType)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyKey, source.BuilderProperties.GetValue(TelemetryUtil.StrategyKey, null!))); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ResultType, args.Context.GetResultType())); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExceptionName, args.Outcome?.Exception?.GetType().FullName)); + EnrichmentUtil.Enrich(enrichmentContext, _enrichers); + + Counter.Add(1, enrichmentContext.TagsSpan); + + EnrichmentContext.Return(enrichmentContext); } private void LogEvent(TelemetryEventArguments args) diff --git a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs index 34b7b5a485..b9c275ff24 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs @@ -1,5 +1,6 @@ using System.Diagnostics.Metrics; using Microsoft.Extensions.Logging; +using Polly.Extensions.Telemetry; using Polly.Extensions.Utils; using Polly.Utils; @@ -71,26 +72,33 @@ protected override async ValueTask> ExecuteCoreAsync CreateOutcome(Outcome outcome) => - outcome.HasResult ? - new Outcome(outcome.Result) : - new Outcome(outcome.Exception!); + private static Outcome CreateOutcome(Outcome outcome) => outcome.HasResult ? + new Outcome(outcome.Result) : + new Outcome(outcome.Exception!); + + private void RecordDuration(ResilienceContext context, Outcome outcome, TimeSpan duration) + { + if (!ExecutionDuration.Enabled) + { + return; + } + + var enrichmentContext = EnrichmentContext.Get(context, null, CreateOutcome(outcome)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.BuilderName, _builderName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyKey, _strategyKey)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ResultType, context.GetResultType())); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExceptionName, outcome.Exception?.GetType().FullName)); + enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExecutionHealth, context.GetExecutionHealth())); + EnrichmentUtil.Enrich(enrichmentContext, _enrichers); + + ExecutionDuration.Record(duration.TotalMilliseconds, enrichmentContext.TagsSpan); + EnrichmentContext.Return(enrichmentContext); + } private object? ExpandOutcome(ResilienceContext context, Outcome outcome) { diff --git a/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs b/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs new file mode 100644 index 0000000000..a75aa1cdb0 --- /dev/null +++ b/test/Polly.Core.Tests/Telemetry/TelemetryEventArgumentsTests.cs @@ -0,0 +1,39 @@ +using System; +using Polly.Telemetry; + +namespace Polly.Extensions.Tests.Telemetry; + +public class TelemetryEventArgumentsTests +{ + private readonly ResilienceTelemetrySource _source = new("builder", new ResilienceProperties(), "strategy", "type"); + + [Fact] + public void Get_Ok() + { + var context = ResilienceContext.Get(); + var args = TelemetryEventArguments.Get(_source, "ev", context, new Outcome("dummy"), "arg"); + + args.Outcome!.Value.Result.Should().Be("dummy"); + args.Context.Should().Be(context); + args.EventName.Should().Be("ev"); + args.Source.Should().Be(_source); + args.Arguments.Should().BeEquivalentTo("arg"); + args.Context.Should().Be(context); + } + + [Fact] + public void Return_EnsurePropertiesCleared() + { + var context = ResilienceContext.Get(); + var args = TelemetryEventArguments.Get(_source, "ev", context, new Outcome("dummy"), "arg"); + + TelemetryEventArguments.Return(args); + + args.Outcome.Should().BeNull(); + args.Context.Should().BeNull(); + args.EventName.Should().BeNull(); + args.Source.Should().BeNull(); + args.Arguments.Should().BeNull(); + args.Context.Should().BeNull(); + } +} diff --git a/test/Polly.Core.Tests/Utils/ObjectPoolTests.cs b/test/Polly.Core.Tests/Utils/ObjectPoolTests.cs index 738c7080ae..b9383df28c 100644 --- a/test/Polly.Core.Tests/Utils/ObjectPoolTests.cs +++ b/test/Polly.Core.Tests/Utils/ObjectPoolTests.cs @@ -8,7 +8,7 @@ public class ObjectPoolTests public void GetAnd_ReturnObject_SameInstance() { // Arrange - var pool = new ObjectPool(() => new object(), _ => true); + var pool = new ObjectPool(() => new object(), _ => { }); var obj1 = pool.Get(); pool.Return(obj1); diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs index ed5983c14c..2ebce93132 100644 --- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs @@ -151,50 +151,39 @@ public void WriteEvent_MeteringWithoutEnrichers_Ok(bool noOutcome, bool exceptio } } - [InlineData(true)] - [InlineData(false)] + [InlineData(1)] + [InlineData(100)] [Theory] - public void WriteEvent_MeteringWithEnrichers_Ok(bool noOutcome) + public void WriteEvent_MeteringWithEnrichers_Ok(int count) { + const int DefaultDimensions = 7; var telemetry = Create(enrichers => { enrichers.Add(context => { - if (noOutcome) + for (int i = 0; i < count; i++) { - context.Outcome.Should().BeNull(); + context.Tags.Add(new KeyValuePair($"custom-{i}", $"custom-{i}-value")); } - else - { - context.Outcome!.Value.Result.Should().Be(true); - } - - context.Context.Should().NotBeNull(); - context.Arguments.Should().BeOfType(); - context.Tags.Add(new KeyValuePair("custom-1", "custom-1-value")); }); enrichers.Add(context => { - context.Tags.Add(new KeyValuePair("custom-2", "custom-2-value")); + context.Tags.Add(new KeyValuePair("other", "other-value")); }); }); - ReportEvent(telemetry, noOutcome ? null : new Outcome(true)); - ReportEvent(telemetry, noOutcome ? null : new Outcome(true)); + ReportEvent(telemetry, new Outcome(true)); var events = GetEvents("resilience-events"); var ev = events[0]; - ev.Count.Should().Be(9); + ev.Count.Should().Be(DefaultDimensions + count + 1); + ev["other"].Should().Be("other-value"); - ev["custom-1"].Should().Be("custom-1-value"); - ev["custom-2"].Should().Be("custom-2-value"); - - ev = events[1]; - ev.Count.Should().Be(9); - - ev["custom-1"].Should().Be("custom-1-value"); - ev["custom-2"].Should().Be("custom-2-value"); + for (int i = 0; i < count; i++) + { + ev[$"custom-{i}"].Should().Be($"custom-{i}-value"); + } } [Fact] diff --git a/test/Polly.TestUtils/TestUtilities.cs b/test/Polly.TestUtils/TestUtilities.cs index 08eb7b849f..540e83d437 100644 --- a/test/Polly.TestUtils/TestUtilities.cs +++ b/test/Polly.TestUtils/TestUtilities.cs @@ -88,7 +88,7 @@ public static void ReportEvent( object arguments) #pragma warning restore S107 // Methods should not have too many parameters { - source.Write(eventName, new TelemetryEventArguments( + source.Write(eventName, TelemetryEventArguments.Get( new ResilienceTelemetrySource(builderName, builderProperties, strategyName, strategyType), eventName, context, @@ -110,6 +110,13 @@ private sealed class CallbackDiagnosticSource : DiagnosticSource public override bool IsEnabled(string name) => true; - public override void Write(string name, object? value) => _callback((TelemetryEventArguments)value!); + public override void Write(string name, object? value) + { + var args = (TelemetryEventArguments)value!; + + // copy the args because these are pooled and in tests we want to preserve them + args = TelemetryEventArguments.Get(args.Source, args.EventName, args.Context, args.Outcome, args.Arguments); + _callback(args); + } } }