diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs index f45ef12c19..9872f400f5 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs @@ -57,8 +57,7 @@ internal async Task IsolateAsync(ResilienceContext context) /// Thrown when calling this method after this object is disposed. public async Task IsolateAsync(CancellationToken cancellationToken = default) { - var context = ResilienceContext.Get().Initialize(isSynchronous: false); - context.CancellationToken = cancellationToken; + var context = ResilienceContext.Get(cancellationToken).Initialize(isSynchronous: false); try { @@ -99,8 +98,7 @@ internal async Task CloseAsync(ResilienceContext context) /// Thrown when calling this method after this object is disposed. public async Task CloseAsync(CancellationToken cancellationToken = default) { - var context = ResilienceContext.Get(); - context.CancellationToken = cancellationToken; + var context = ResilienceContext.Get(cancellationToken); try { diff --git a/src/Polly.Core/ResilienceContext.cs b/src/Polly.Core/ResilienceContext.cs index 5ada98899c..fadae740a2 100644 --- a/src/Polly.Core/ResilienceContext.cs +++ b/src/Polly.Core/ResilienceContext.cs @@ -9,7 +9,7 @@ namespace Polly; /// /// /// Do not re-use an instance of across more than one execution. The is retrieved from the pool -/// by calling the method. After you are done with it you should return it to the pool by calling the method. +/// by calling the method. After you are done with it you should return it to the pool by calling the method. /// public sealed class ResilienceContext { @@ -23,6 +23,19 @@ private ResilienceContext() { } + /// + /// Gets a key unique to the call site of the current execution. + /// + /// + /// Resilience strategy instances are commonly reused across multiple call sites. + /// Set an so that logging and metrics can distinguish usages of policy instances at different call sites. + /// The operation key value should have a low cardinality (i.e. do not assign values such as to this property). + /// + /// Defaults to . + /// + /// + public string? OperationKey { get; private set; } + /// /// Gets or sets the associated with the execution. /// @@ -69,15 +82,40 @@ private ResilienceContext() /// /// Gets a instance from the pool. /// + /// The cancellation token. + /// An instance of . + /// + /// After the execution is finished you should return the back to the pool + /// by calling method. + /// + public static ResilienceContext Get(CancellationToken cancellationToken = default) + { + var context = Pool.Get(); + context.CancellationToken = cancellationToken; + return context; + } + + /// + /// Gets a instance from the pool. + /// + /// An operation key associated with the context. + /// The cancellation token. /// An instance of . /// /// After the execution is finished you should return the back to the pool /// by calling method. /// - public static ResilienceContext Get() => Pool.Get(); + public static ResilienceContext Get(string operationKey, CancellationToken cancellationToken = default) + { + var context = Pool.Get(); + context.OperationKey = operationKey; + context.CancellationToken = cancellationToken; + return context; + } internal void InitializeFrom(ResilienceContext context) { + OperationKey = context.OperationKey; ResultType = context.ResultType; IsSynchronous = context.IsSynchronous; CancellationToken = context.CancellationToken; @@ -121,6 +159,7 @@ internal void AddResilienceEvent(ResilienceEvent @event) internal bool Reset() { + OperationKey = null; IsSynchronous = false; ResultType = typeof(UnknownResult); ContinueOnCapturedContext = false; diff --git a/src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs b/src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs index a13ac39973..332cfe183f 100644 --- a/src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs +++ b/src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs @@ -191,8 +191,7 @@ static async (context, state) => private static ResilienceContext GetAsyncContext(CancellationToken cancellationToken) { - var context = ResilienceContext.Get(); - context.CancellationToken = cancellationToken; + var context = ResilienceContext.Get(cancellationToken); InitializeAsyncContext(context); diff --git a/src/Polly.Core/ResilienceStrategy.SyncT.cs b/src/Polly.Core/ResilienceStrategy.SyncT.cs index 2a99db62f5..8f26ae7fd0 100644 --- a/src/Polly.Core/ResilienceStrategy.SyncT.cs +++ b/src/Polly.Core/ResilienceStrategy.SyncT.cs @@ -232,8 +232,7 @@ public TResult Execute( private static ResilienceContext GetSyncContext(CancellationToken cancellationToken) { - var context = ResilienceContext.Get(); - context.CancellationToken = cancellationToken; + var context = ResilienceContext.Get(cancellationToken); InitializeSyncContext(context); diff --git a/src/Polly.Extensions/Telemetry/Log.cs b/src/Polly.Extensions/Telemetry/Log.cs index c68896994a..06ec3b794e 100644 --- a/src/Polly.Extensions/Telemetry/Log.cs +++ b/src/Polly.Extensions/Telemetry/Log.cs @@ -16,6 +16,7 @@ internal static partial class Log "Strategy Name: '{StrategyName}', " + "Strategy Type: '{StrategyType}', " + "Strategy Key: '{StrategyKey}', " + + "Operation Key: '{OperationKey}', " + "Result: '{Result}'", EventName = "ResilienceEvent")] public static partial void ResilienceEvent( @@ -26,6 +27,7 @@ public static partial void ResilienceEvent( string? strategyName, string strategyType, string? strategyKey, + string? operationKey, object? result, Exception? exception); @@ -35,12 +37,14 @@ public static partial void ResilienceEvent( "Resilience strategy executing. " + "Builder Name: '{BuilderName}', " + "Strategy Key: '{StrategyKey}', " + + "Operation Key: '{OperationKey}', " + "Result Type: '{ResultType}'", EventName = "StrategyExecuting")] public static partial void ExecutingStrategy( this ILogger logger, string? builderName, string? strategyKey, + string? operationKey, string resultType); [LoggerMessage( @@ -48,6 +52,7 @@ public static partial void ExecutingStrategy( Message = "Resilience strategy executed. " + "Builder Name: '{BuilderName}', " + "Strategy Key: '{StrategyKey}', " + + "Operation Key: '{OperationKey}', " + "Result Type: '{ResultType}', " + "Result: '{Result}', " + "Execution Health: '{ExecutionHealth}', " + @@ -58,6 +63,7 @@ public static partial void StrategyExecuted( LogLevel logLevel, string? builderName, string? strategyKey, + string? operationKey, string resultType, object? result, string executionHealth, @@ -71,6 +77,7 @@ public static partial void StrategyExecuted( "Strategy Name: '{StrategyName}', " + "Strategy Type: '{StrategyType}', " + "Strategy Key: '{StrategyKey}', " + + "Operation Key: '{OperationKey}', " + "Result: '{Result}', " + "Handled: '{Handled}', " + "Attempt: '{Attempt}', " + @@ -84,6 +91,7 @@ public static partial void ExecutionAttempt( string? strategyName, string strategyType, string? strategyKey, + string? operationKey, object? result, bool handled, int attempt, diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs index 8c9540826f..630227d6dd 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs @@ -53,6 +53,7 @@ private static void AddCommonTags(TelemetryEventArguments args, ResilienceTeleme 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.OperationKey, enrichmentContext.Context.OperationKey)); enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ResultType, args.Context.GetResultType())); enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExceptionName, args.Outcome?.Exception?.GetType().FullName)); } @@ -112,6 +113,7 @@ private void LogEvent(TelemetryEventArguments args) args.Source.StrategyName, args.Source.StrategyType, strategyKey, + args.Context.OperationKey, result, executionAttempt.Handled, executionAttempt.Attempt, @@ -121,7 +123,17 @@ private void LogEvent(TelemetryEventArguments args) } else { - Log.ResilienceEvent(_logger, level, args.Event.EventName, args.Source.BuilderName, args.Source.StrategyName, args.Source.StrategyType, strategyKey, result, args.Outcome?.Exception); + Log.ResilienceEvent( + _logger, + level, + args.Event.EventName, + args.Source.BuilderName, + args.Source.StrategyName, + args.Source.StrategyType, + strategyKey, + args.Context.OperationKey, + result, + args.Outcome?.Exception); } } } diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs index 0bf298bf67..cc1f26c49f 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs @@ -16,6 +16,8 @@ internal class ResilienceTelemetryTags public const string ResultType = "result-type"; + public const string OperationKey = "operation-key"; + public const string ExceptionName = "exception-name"; public const string ExecutionHealth = "execution-health"; @@ -23,5 +25,4 @@ internal class ResilienceTelemetryTags public const string AttemptNumber = "attempt-number"; public const string AttemptHandled = "attempt-handled"; - } diff --git a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs index 5082e456b9..d06ee65327 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs @@ -51,7 +51,7 @@ protected override async ValueTask> ExecuteCoreAsync> ExecuteCoreAsync(ResilienceContext context, Outcome 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.OperationKey, context.OperationKey)); 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())); diff --git a/src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs b/src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs index 9972a6f3c4..1ff3408571 100644 --- a/src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs +++ b/src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs @@ -10,8 +10,7 @@ public static ResilienceContext Create( bool continueOnCapturedContext, out IDictionary oldProperties) { - var resilienceContext = ResilienceContext.Get(); - resilienceContext.CancellationToken = cancellationToken; + var resilienceContext = ResilienceContext.Get(context.OperationKey, cancellationToken); resilienceContext.ContinueOnCapturedContext = continueOnCapturedContext; resilienceContext.Properties.SetProperties(context, out oldProperties); diff --git a/test/Polly.Core.Tests/ResilienceContextTests.cs b/test/Polly.Core.Tests/ResilienceContextTests.cs index 67e28939fa..6d8281489a 100644 --- a/test/Polly.Core.Tests/ResilienceContextTests.cs +++ b/test/Polly.Core.Tests/ResilienceContextTests.cs @@ -18,6 +18,29 @@ public void Get_EnsureDefaults() AssertDefaults(context); } + [Fact] + public void Get_CancellationToken_Ok() + { + using var token = new CancellationTokenSource(); + + var context = ResilienceContext.Get(token.Token); + + context.CancellationToken.Should().Be(token.Token); + } + + [InlineData(null)] + [InlineData("")] + [InlineData("some-key")] + [Theory] + public void Get_OperationKeyAndCancellationToken_Ok(string? key) + { + using var token = new CancellationTokenSource(); + + var context = ResilienceContext.Get(key!, token.Token); + context.OperationKey.Should().Be(key); + context.CancellationToken.Should().Be(token.Token); + } + [Fact] public async Task Get_EnsurePooled() { @@ -86,7 +109,7 @@ public void Initialize_Typed_Ok(bool synchronous) [Theory] public void Initialize_From_Ok(bool synchronous) { - var context = ResilienceContext.Get(); + var context = ResilienceContext.Get("some-key"); context.Initialize(synchronous); context.ContinueOnCapturedContext = true; @@ -98,6 +121,7 @@ public void Initialize_From_Ok(bool synchronous) other.IsInitialized.Should().BeTrue(); other.IsSynchronous.Should().Be(synchronous); other.ContinueOnCapturedContext.Should().BeTrue(); + other.OperationKey.Should().Be("some-key"); } [InlineData(true)] @@ -125,5 +149,6 @@ private static void AssertDefaults(ResilienceContext context) context.CancellationToken.Should().Be(CancellationToken.None); context.Properties.Should().BeEmpty(); context.ResilienceEvents.Should().BeEmpty(); + context.OperationKey.Should().BeNull(); } } diff --git a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs index 1142e3ed35..e181d708f6 100644 --- a/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/ResilienceTelemetryDiagnosticSourceTests.cs @@ -71,11 +71,11 @@ public void WriteEvent_LoggingWithOutcome_Ok(bool noOutcome) if (noOutcome) { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: ''"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: ''"); } else { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '200'"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: '200'"); } } @@ -98,11 +98,11 @@ public void WriteEvent_LoggingWithException_Ok(bool noOutcome) if (noOutcome) { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: ''"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: ''"); } else { - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: 'Dummy message.'"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: 'Dummy message.'"); } } @@ -114,7 +114,7 @@ public void WriteEvent_LoggingWithoutStrategyKey_Ok() var messages = _logger.GetRecords(new EventId(0, "ResilienceEvent")).ToList(); - messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: '', Result: ''"); + messages[0].Message.Should().Be("Resilience event occurred. EventName: 'my-event', Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: '', Operation Key: 'op-key', Result: ''"); } [InlineData(ResilienceEventSeverity.Error, LogLevel.Error)] @@ -149,7 +149,7 @@ public void WriteExecutionAttempt_LoggingWithException_Ok() var messages = _logger.GetRecords(new EventId(3, "ExecutionAttempt")).ToList(); messages.Should().HaveCount(1); - messages[0].Message.Should().Be("Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: 'Dummy message.', Handled: 'True', Attempt: '4', Execution Time: '123'"); + messages[0].Message.Should().Be("Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: 'Dummy message.', Handled: 'True', Attempt: '4', Execution Time: '123'"); } [InlineData(true, true)] @@ -169,11 +169,11 @@ public void WriteExecutionAttempt_LoggingWithOutcome_Ok(bool noOutcome, bool han if (noOutcome) { string resultString = string.Empty; - messages[0].Message.Should().Be($"Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '{resultString}', Handled: '{handled}', Attempt: '4', Execution Time: '123'"); + messages[0].Message.Should().Be($"Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: '{resultString}', Handled: '{handled}', Attempt: '4', Execution Time: '123'"); } else { - messages[0].Message.Should().Be($"Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Result: '200', Handled: '{handled}', Attempt: '4', Execution Time: '123'"); + messages[0].Message.Should().Be($"Execution attempt. Builder Name: 'my-builder', Strategy Name: 'my-strategy', Strategy Type: 'my-strategy-type', Strategy Key: 'my-strategy-key', Operation Key: 'op-key', Result: '200', Handled: '{handled}', Attempt: '4', Execution Time: '123'"); } messages[0].LogLevel.Should().Be(LogLevel.Warning); @@ -205,18 +205,19 @@ public void WriteEvent_MeteringWithoutEnrichers_Ok(bool noOutcome, bool exceptio true when exception => Outcome.FromException(new InvalidOperationException("Dummy message.")), _ => Outcome.FromResult(true) }; - ReportEvent(telemetry, outcome, context: ResilienceContext.Get().WithResultType()); + ReportEvent(telemetry, outcome, context: ResilienceContext.Get("op-key").WithResultType()); var events = GetEvents("resilience-events"); events.Should().HaveCount(1); var ev = events[0]; - ev.Count.Should().Be(8); + ev.Count.Should().Be(9); ev["event-name"].Should().Be("my-event"); ev["event-severity"].Should().Be("Warning"); ev["strategy-type"].Should().Be("my-strategy-type"); ev["strategy-name"].Should().Be("my-strategy"); ev["strategy-key"].Should().Be("my-strategy-key"); + ev["operation-key"].Should().Be("op-key"); ev["builder-name"].Should().Be("my-builder"); ev["result-type"].Should().Be("Boolean"); @@ -245,18 +246,19 @@ public void WriteExecutionAttemptEvent_Metering_Ok(bool noOutcome, bool exceptio true when exception => Outcome.FromException(new InvalidOperationException("Dummy message.")), _ => Outcome.FromResult(true) }; - ReportEvent(telemetry, outcome, context: ResilienceContext.Get().WithResultType(), arg: attemptArg); + ReportEvent(telemetry, outcome, context: ResilienceContext.Get("op-key").WithResultType(), arg: attemptArg); var events = GetEvents("execution-attempt-duration"); events.Should().HaveCount(1); var ev = events[0]; - ev.Count.Should().Be(10); + ev.Count.Should().Be(11); ev["event-name"].Should().Be("my-event"); ev["event-severity"].Should().Be("Warning"); ev["strategy-type"].Should().Be("my-strategy-type"); ev["strategy-name"].Should().Be("my-strategy"); ev["strategy-key"].Should().Be("my-strategy-key"); + ev["operation-key"].Should().Be("op-key"); ev["builder-name"].Should().Be("my-builder"); ev["result-type"].Should().Be("Boolean"); ev["attempt-number"].Should().Be(5); @@ -279,7 +281,7 @@ public void WriteExecutionAttemptEvent_Metering_Ok(bool noOutcome, bool exceptio [Theory] public void WriteEvent_MeteringWithEnrichers_Ok(int count) { - const int DefaultDimensions = 7; + const int DefaultDimensions = 8; var telemetry = Create(enrichers => { enrichers.Add(context => @@ -339,7 +341,7 @@ private static void ReportEvent( object? arg = null, ResilienceEventSeverity severity = ResilienceEventSeverity.Warning) { - context ??= ResilienceContext.Get(); + context ??= ResilienceContext.Get("op-key"); var props = new ResilienceProperties(); if (!string.IsNullOrEmpty(strategyKey)) { diff --git a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs index 1836006807..b5f708bf1e 100644 --- a/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs +++ b/test/Polly.Extensions.Tests/Telemetry/TelemetryResilienceStrategyTests.cs @@ -46,20 +46,20 @@ public void Execute_EnsureLogged(bool healthy) ((List)c.ResilienceEvents).Add(new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy")); } }, - ResilienceContext.Get(), string.Empty); + ResilienceContext.Get("op-key"), string.Empty); var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList(); messages.Should().HaveCount(1); - messages[0].Message.Should().Be("Resilience strategy executing. Builder Name: 'my-builder', Strategy Key: 'my-key', Result Type: 'void'"); + messages[0].Message.Should().Be("Resilience strategy executing. Builder Name: 'my-builder', Strategy Key: 'my-key', Operation Key: 'op-key', Result Type: 'void'"); messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList(); messages.Should().HaveCount(1); - messages[0].Message.Should().Match($"Resilience strategy executed. Builder Name: 'my-builder', Strategy Key: 'my-key', Result Type: 'void', Result: 'void', Execution Health: '{healthString}', Execution Time: *ms"); + messages[0].Message.Should().Match($"Resilience strategy executed. Builder Name: 'my-builder', Strategy Key: 'my-key', Operation Key: 'op-key', Result Type: 'void', Result: 'void', Execution Health: '{healthString}', Execution Time: *ms"); messages[0].LogLevel.Should().Be(healthy ? LogLevel.Debug : LogLevel.Warning); // verify reported state var coll = messages[0].State.Should().BeAssignableTo>>().Subject; - coll.Count.Should().Be(7); - coll.AsEnumerable().Should().HaveCount(7); + coll.Count.Should().Be(8); + coll.AsEnumerable().Should().HaveCount(8); (coll as IEnumerable).GetEnumerator().Should().NotBeNull(); for (int i = 0; i < coll.Count; i++) @@ -74,15 +74,15 @@ public void Execute_EnsureLogged(bool healthy) public void Execute_WithException_EnsureLogged() { var strategy = CreateStrategy(); - strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."))).Should().Throw(); + strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."), ResilienceContext.Get("op-key"))).Should().Throw(); var messages = _logger.GetRecords(new EventId(1, "StrategyExecuting")).ToList(); messages.Should().HaveCount(1); - messages[0].Message.Should().Be("Resilience strategy executing. Builder Name: 'my-builder', Strategy Key: 'my-key', Result Type: 'void'"); + messages[0].Message.Should().Be("Resilience strategy executing. Builder Name: 'my-builder', Strategy Key: 'my-key', Operation Key: 'op-key', Result Type: 'void'"); messages = _logger.GetRecords(new EventId(2, "StrategyExecuted")).ToList(); messages.Should().HaveCount(1); - messages[0].Message.Should().Match($"Resilience strategy executed. Builder Name: 'my-builder', Strategy Key: 'my-key', Result Type: 'void', Result: 'Dummy message.', Execution Health: 'Healthy', Execution Time: *ms"); + messages[0].Message.Should().Match($"Resilience strategy executed. Builder Name: 'my-builder', Strategy Key: 'my-key', Operation Key: 'op-key', Result Type: 'void', Result: 'Dummy message.', Execution Health: 'Healthy', Execution Time: *ms"); messages[0].Exception.Should().BeOfType(); } @@ -115,12 +115,13 @@ public void Execute_WithResult_EnsureEnrichmentContextWithCorrectOutcome() public void Execute_WithException_EnsureMetered() { var strategy = CreateStrategy(); - strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."))).Should().Throw(); + strategy.Invoking(s => s.Execute(_ => throw new InvalidOperationException("Dummy message."), ResilienceContext.Get("op-key"))).Should().Throw(); var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags; - ev.Count.Should().Be(5); + ev.Count.Should().Be(6); ev["strategy-key"].Should().Be("my-key"); + ev["operation-key"].Should().Be("op-key"); ev["builder-name"].Should().Be("my-builder"); ev["result-type"].Should().Be("void"); ev["exception-name"].Should().Be("System.InvalidOperationException"); @@ -139,7 +140,7 @@ public void Execute_Enrichers_Ok() var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags; - ev.Count.Should().Be(6); + ev.Count.Should().Be(7); ev["my-custom-tag"].Should().Be("my-tag-value"); } @@ -159,12 +160,13 @@ public void Execute_WithResult_EnsureMetered(bool healthy) return true; }, - ResilienceContext.Get(), string.Empty); + ResilienceContext.Get("op-key"), string.Empty); var ev = _events.Single(v => v.Name == "strategy-execution-duration").Tags; - ev.Count.Should().Be(5); + ev.Count.Should().Be(6); ev["strategy-key"].Should().Be("my-key"); + ev["operation-key"].Should().Be("op-key"); ev["builder-name"].Should().Be("my-builder"); ev["result-type"].Should().Be("Boolean"); ev["exception-name"].Should().BeNull(); diff --git a/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs b/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs index 873b4550f2..5eacbba7d9 100644 --- a/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs +++ b/test/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs @@ -26,6 +26,7 @@ public ResilienceStrategyConversionExtensionsTests() context.IsSynchronous.Should().Be(_isSynchronous); context.Properties.Set(Outgoing, "outgoing-value"); context.Properties.GetValue(Incoming, string.Empty).Should().Be("incoming-value"); + context.OperationKey.Should().Be("op-key"); if (_resultType != null) { @@ -44,7 +45,7 @@ public void AsSyncPolicy_Ok() { _isVoid = true; _isSynchronous = true; - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -64,7 +65,7 @@ public void AsSyncPolicy_Generic_Ok() _isVoid = false; _isSynchronous = true; _resultType = typeof(string); - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -80,7 +81,7 @@ public void AsSyncPolicy_Result_Ok() _isVoid = false; _isSynchronous = true; _resultType = typeof(string); - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -96,7 +97,7 @@ public async Task AsAsyncPolicy_Ok() { _isVoid = true; _isSynchronous = false; - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -117,7 +118,7 @@ public async Task AsAsyncPolicy_Generic_Ok() _isVoid = false; _isSynchronous = false; _resultType = typeof(string); - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -138,7 +139,7 @@ public async Task AsAsyncPolicy_Result_Ok() _isVoid = false; _isSynchronous = false; _resultType = typeof(string); - var context = new Context + var context = new Context("op-key") { [Incoming.Key] = "incoming-value" }; @@ -168,7 +169,7 @@ public void RetryStrategy_AsSyncPolicy_Ok() .Build() .AsSyncPolicy(); - var context = new Context + var context = new Context("op-key") { ["retry"] = 0 };