Skip to content

Commit a806318

Browse files
authored
Introduce ResilienceContext.OperationKey (#1380)
1 parent 158a2f8 commit a806318

13 files changed

+137
-50
lines changed

src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ internal async Task IsolateAsync(ResilienceContext context)
5757
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
5858
public async Task IsolateAsync(CancellationToken cancellationToken = default)
5959
{
60-
var context = ResilienceContext.Get().Initialize<VoidResult>(isSynchronous: false);
61-
context.CancellationToken = cancellationToken;
60+
var context = ResilienceContext.Get(cancellationToken).Initialize<VoidResult>(isSynchronous: false);
6261

6362
try
6463
{
@@ -99,8 +98,7 @@ internal async Task CloseAsync(ResilienceContext context)
9998
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
10099
public async Task CloseAsync(CancellationToken cancellationToken = default)
101100
{
102-
var context = ResilienceContext.Get();
103-
context.CancellationToken = cancellationToken;
101+
var context = ResilienceContext.Get(cancellationToken);
104102

105103
try
106104
{

src/Polly.Core/ResilienceContext.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Polly;
99
/// </summary>
1010
/// <remarks>
1111
/// Do not re-use an instance of <see cref="ResilienceContext"/> across more than one execution. The <see cref="ResilienceContext"/> is retrieved from the pool
12-
/// by calling the <see cref="Get"/> method. After you are done with it you should return it to the pool by calling the <see cref="Return"/> method.
12+
/// by calling the <see cref="Get(CancellationToken)"/> method. After you are done with it you should return it to the pool by calling the <see cref="Return"/> method.
1313
/// </remarks>
1414
public sealed class ResilienceContext
1515
{
@@ -23,6 +23,19 @@ private ResilienceContext()
2323
{
2424
}
2525

26+
/// <summary>
27+
/// Gets a key unique to the call site of the current execution.
28+
/// </summary>
29+
/// <remarks>
30+
/// Resilience strategy instances are commonly reused across multiple call sites.
31+
/// Set an <see cref="OperationKey"/> so that logging and metrics can distinguish usages of policy instances at different call sites.
32+
/// The operation key value should have a low cardinality (i.e. do not assign values such as <see cref="Guid"/> to this property).
33+
/// <para>
34+
/// Defaults to <see langword="null"/>.
35+
/// </para>
36+
/// </remarks>
37+
public string? OperationKey { get; private set; }
38+
2639
/// <summary>
2740
/// Gets or sets the <see cref="CancellationToken"/> associated with the execution.
2841
/// </summary>
@@ -69,15 +82,40 @@ private ResilienceContext()
6982
/// <summary>
7083
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
7184
/// </summary>
85+
/// <param name="cancellationToken">The cancellation token.</param>
86+
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
87+
/// <remarks>
88+
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
89+
/// by calling <see cref="Return(ResilienceContext)"/> method.
90+
/// </remarks>
91+
public static ResilienceContext Get(CancellationToken cancellationToken = default)
92+
{
93+
var context = Pool.Get();
94+
context.CancellationToken = cancellationToken;
95+
return context;
96+
}
97+
98+
/// <summary>
99+
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
100+
/// </summary>
101+
/// <param name="operationKey">An operation key associated with the context.</param>
102+
/// <param name="cancellationToken">The cancellation token.</param>
72103
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
73104
/// <remarks>
74105
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
75106
/// by calling <see cref="Return(ResilienceContext)"/> method.
76107
/// </remarks>
77-
public static ResilienceContext Get() => Pool.Get();
108+
public static ResilienceContext Get(string operationKey, CancellationToken cancellationToken = default)
109+
{
110+
var context = Pool.Get();
111+
context.OperationKey = operationKey;
112+
context.CancellationToken = cancellationToken;
113+
return context;
114+
}
78115

79116
internal void InitializeFrom(ResilienceContext context)
80117
{
118+
OperationKey = context.OperationKey;
81119
ResultType = context.ResultType;
82120
IsSynchronous = context.IsSynchronous;
83121
CancellationToken = context.CancellationToken;
@@ -121,6 +159,7 @@ internal void AddResilienceEvent(ResilienceEvent @event)
121159

122160
internal bool Reset()
123161
{
162+
OperationKey = null;
124163
IsSynchronous = false;
125164
ResultType = typeof(UnknownResult);
126165
ContinueOnCapturedContext = false;

src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,7 @@ static async (context, state) =>
191191

192192
private static ResilienceContext GetAsyncContext<TResult>(CancellationToken cancellationToken)
193193
{
194-
var context = ResilienceContext.Get();
195-
context.CancellationToken = cancellationToken;
194+
var context = ResilienceContext.Get(cancellationToken);
196195

197196
InitializeAsyncContext<TResult>(context);
198197

src/Polly.Core/ResilienceStrategy.SyncT.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,7 @@ public TResult Execute<TResult, TState>(
232232

233233
private static ResilienceContext GetSyncContext<TResult>(CancellationToken cancellationToken)
234234
{
235-
var context = ResilienceContext.Get();
236-
context.CancellationToken = cancellationToken;
235+
var context = ResilienceContext.Get(cancellationToken);
237236

238237
InitializeSyncContext<TResult>(context);
239238

src/Polly.Extensions/Telemetry/Log.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal static partial class Log
1616
"Strategy Name: '{StrategyName}', " +
1717
"Strategy Type: '{StrategyType}', " +
1818
"Strategy Key: '{StrategyKey}', " +
19+
"Operation Key: '{OperationKey}', " +
1920
"Result: '{Result}'",
2021
EventName = "ResilienceEvent")]
2122
public static partial void ResilienceEvent(
@@ -26,6 +27,7 @@ public static partial void ResilienceEvent(
2627
string? strategyName,
2728
string strategyType,
2829
string? strategyKey,
30+
string? operationKey,
2931
object? result,
3032
Exception? exception);
3133

@@ -35,19 +37,22 @@ public static partial void ResilienceEvent(
3537
"Resilience strategy executing. " +
3638
"Builder Name: '{BuilderName}', " +
3739
"Strategy Key: '{StrategyKey}', " +
40+
"Operation Key: '{OperationKey}', " +
3841
"Result Type: '{ResultType}'",
3942
EventName = "StrategyExecuting")]
4043
public static partial void ExecutingStrategy(
4144
this ILogger logger,
4245
string? builderName,
4346
string? strategyKey,
47+
string? operationKey,
4448
string resultType);
4549

4650
[LoggerMessage(
4751
EventId = 2,
4852
Message = "Resilience strategy executed. " +
4953
"Builder Name: '{BuilderName}', " +
5054
"Strategy Key: '{StrategyKey}', " +
55+
"Operation Key: '{OperationKey}', " +
5156
"Result Type: '{ResultType}', " +
5257
"Result: '{Result}', " +
5358
"Execution Health: '{ExecutionHealth}', " +
@@ -58,6 +63,7 @@ public static partial void StrategyExecuted(
5863
LogLevel logLevel,
5964
string? builderName,
6065
string? strategyKey,
66+
string? operationKey,
6167
string resultType,
6268
object? result,
6369
string executionHealth,
@@ -71,6 +77,7 @@ public static partial void StrategyExecuted(
7177
"Strategy Name: '{StrategyName}', " +
7278
"Strategy Type: '{StrategyType}', " +
7379
"Strategy Key: '{StrategyKey}', " +
80+
"Operation Key: '{OperationKey}', " +
7481
"Result: '{Result}', " +
7582
"Handled: '{Handled}', " +
7683
"Attempt: '{Attempt}', " +
@@ -84,6 +91,7 @@ public static partial void ExecutionAttempt(
8491
string? strategyName,
8592
string strategyType,
8693
string? strategyKey,
94+
string? operationKey,
8795
object? result,
8896
bool handled,
8997
int attempt,

src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private static void AddCommonTags(TelemetryEventArguments args, ResilienceTeleme
5353
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyName, source.StrategyName));
5454
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyType, source.StrategyType));
5555
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyKey, source.BuilderProperties.GetValue(TelemetryUtil.StrategyKey, null!)));
56+
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.OperationKey, enrichmentContext.Context.OperationKey));
5657
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ResultType, args.Context.GetResultType()));
5758
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExceptionName, args.Outcome?.Exception?.GetType().FullName));
5859
}
@@ -112,6 +113,7 @@ private void LogEvent(TelemetryEventArguments args)
112113
args.Source.StrategyName,
113114
args.Source.StrategyType,
114115
strategyKey,
116+
args.Context.OperationKey,
115117
result,
116118
executionAttempt.Handled,
117119
executionAttempt.Attempt,
@@ -121,7 +123,17 @@ private void LogEvent(TelemetryEventArguments args)
121123
}
122124
else
123125
{
124-
Log.ResilienceEvent(_logger, level, args.Event.EventName, args.Source.BuilderName, args.Source.StrategyName, args.Source.StrategyType, strategyKey, result, args.Outcome?.Exception);
126+
Log.ResilienceEvent(
127+
_logger,
128+
level,
129+
args.Event.EventName,
130+
args.Source.BuilderName,
131+
args.Source.StrategyName,
132+
args.Source.StrategyType,
133+
strategyKey,
134+
args.Context.OperationKey,
135+
result,
136+
args.Outcome?.Exception);
125137
}
126138
}
127139
}

src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ internal class ResilienceTelemetryTags
1616

1717
public const string ResultType = "result-type";
1818

19+
public const string OperationKey = "operation-key";
20+
1921
public const string ExceptionName = "exception-name";
2022

2123
public const string ExecutionHealth = "execution-health";
2224

2325
public const string AttemptNumber = "attempt-number";
2426

2527
public const string AttemptHandled = "attempt-handled";
26-
2728
}

src/Polly.Extensions/Telemetry/TelemetryResilienceStrategy.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, T
5151
TState state)
5252
{
5353
var stamp = _timeProvider.GetTimestamp();
54-
Log.ExecutingStrategy(_logger, _builderName, _strategyKey, context.GetResultType());
54+
Log.ExecutingStrategy(_logger, _builderName, _strategyKey, context.OperationKey, context.GetResultType());
5555

5656
var outcome = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
5757

@@ -63,6 +63,7 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, T
6363
logLevel,
6464
_builderName,
6565
_strategyKey,
66+
context.OperationKey,
6667
context.GetResultType(),
6768
ExpandOutcome(context, outcome),
6869
context.GetExecutionHealth(),
@@ -88,6 +89,7 @@ private void RecordDuration<TResult>(ResilienceContext context, Outcome<TResult>
8889
var enrichmentContext = EnrichmentContext.Get(context, null, CreateOutcome(outcome));
8990
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.BuilderName, _builderName));
9091
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.StrategyKey, _strategyKey));
92+
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.OperationKey, context.OperationKey));
9193
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ResultType, context.GetResultType()));
9294
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExceptionName, outcome.Exception?.GetType().FullName));
9395
enrichmentContext.Tags.Add(new(ResilienceTelemetryTags.ExecutionHealth, context.GetExecutionHealth()));

src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ public static ResilienceContext Create(
1010
bool continueOnCapturedContext,
1111
out IDictionary<string, object> oldProperties)
1212
{
13-
var resilienceContext = ResilienceContext.Get();
14-
resilienceContext.CancellationToken = cancellationToken;
13+
var resilienceContext = ResilienceContext.Get(context.OperationKey, cancellationToken);
1514
resilienceContext.ContinueOnCapturedContext = continueOnCapturedContext;
1615
resilienceContext.Properties.SetProperties(context, out oldProperties);
1716

test/Polly.Core.Tests/ResilienceContextTests.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,29 @@ public void Get_EnsureDefaults()
1818
AssertDefaults(context);
1919
}
2020

21+
[Fact]
22+
public void Get_CancellationToken_Ok()
23+
{
24+
using var token = new CancellationTokenSource();
25+
26+
var context = ResilienceContext.Get(token.Token);
27+
28+
context.CancellationToken.Should().Be(token.Token);
29+
}
30+
31+
[InlineData(null)]
32+
[InlineData("")]
33+
[InlineData("some-key")]
34+
[Theory]
35+
public void Get_OperationKeyAndCancellationToken_Ok(string? key)
36+
{
37+
using var token = new CancellationTokenSource();
38+
39+
var context = ResilienceContext.Get(key!, token.Token);
40+
context.OperationKey.Should().Be(key);
41+
context.CancellationToken.Should().Be(token.Token);
42+
}
43+
2144
[Fact]
2245
public async Task Get_EnsurePooled()
2346
{
@@ -86,7 +109,7 @@ public void Initialize_Typed_Ok(bool synchronous)
86109
[Theory]
87110
public void Initialize_From_Ok(bool synchronous)
88111
{
89-
var context = ResilienceContext.Get();
112+
var context = ResilienceContext.Get("some-key");
90113
context.Initialize<bool>(synchronous);
91114
context.ContinueOnCapturedContext = true;
92115

@@ -98,6 +121,7 @@ public void Initialize_From_Ok(bool synchronous)
98121
other.IsInitialized.Should().BeTrue();
99122
other.IsSynchronous.Should().Be(synchronous);
100123
other.ContinueOnCapturedContext.Should().BeTrue();
124+
other.OperationKey.Should().Be("some-key");
101125
}
102126

103127
[InlineData(true)]
@@ -125,5 +149,6 @@ private static void AssertDefaults(ResilienceContext context)
125149
context.CancellationToken.Should().Be(CancellationToken.None);
126150
context.Properties.Should().BeEmpty();
127151
context.ResilienceEvents.Should().BeEmpty();
152+
context.OperationKey.Should().BeNull();
128153
}
129154
}

0 commit comments

Comments
 (0)