From a806318b4cc7f81eeefd2f91fccf393cd9680bbd Mon Sep 17 00:00:00 2001
From: martintmk <103487740+martintmk@users.noreply.github.com>
Date: Mon, 3 Jul 2023 11:15:29 +0200
Subject: [PATCH] Introduce `ResilienceContext.OperationKey` (#1380)
---
.../CircuitBreakerManualControl.cs | 6 +--
src/Polly.Core/ResilienceContext.cs | 43 ++++++++++++++++++-
.../ResilienceStrategy.Async.ValueTaskT.cs | 3 +-
src/Polly.Core/ResilienceStrategy.SyncT.cs | 3 +-
src/Polly.Extensions/Telemetry/Log.cs | 8 ++++
.../ResilienceTelemetryDiagnosticSource.cs | 14 +++++-
.../Telemetry/ResilienceTelemetryTags.cs | 3 +-
.../Telemetry/TelemetryResilienceStrategy.cs | 4 +-
.../Wrappers/ResilienceContextFactory.cs | 3 +-
.../ResilienceContextTests.cs | 27 +++++++++++-
...esilienceTelemetryDiagnosticSourceTests.cs | 30 +++++++------
.../TelemetryResilienceStrategyTests.cs | 28 ++++++------
...lienceStrategyConversionExtensionsTests.cs | 15 ++++---
13 files changed, 137 insertions(+), 50 deletions(-)
diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs
index f45ef12c198..9872f400f5e 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 5ada98899c9..fadae740a2d 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 a13ac399736..332cfe183fa 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 2a99db62f58..8f26ae7fd0d 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 c68896994a6..06ec3b794e1 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 8c9540826f5..630227d6dd6 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 0bf298bf67f..cc1f26c49f4 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 5082e456b9c..d06ee65327a 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 9972a6f3c48..1ff3408571e 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 67e28939fad..6d8281489ac 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 1142e3ed357..e181d708f68 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