Skip to content

Commit

Permalink
Introduce ResilienceContext.OperationKey (#1380)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Jul 3, 2023
1 parent 158a2f8 commit a806318
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 50 deletions.
6 changes: 2 additions & 4 deletions src/Polly.Core/CircuitBreaker/CircuitBreakerManualControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ internal async Task IsolateAsync(ResilienceContext context)
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
public async Task IsolateAsync(CancellationToken cancellationToken = default)
{
var context = ResilienceContext.Get().Initialize<VoidResult>(isSynchronous: false);
context.CancellationToken = cancellationToken;
var context = ResilienceContext.Get(cancellationToken).Initialize<VoidResult>(isSynchronous: false);

try
{
Expand Down Expand Up @@ -99,8 +98,7 @@ internal async Task CloseAsync(ResilienceContext context)
/// <exception cref="ObjectDisposedException">Thrown when calling this method after this object is disposed.</exception>
public async Task CloseAsync(CancellationToken cancellationToken = default)
{
var context = ResilienceContext.Get();
context.CancellationToken = cancellationToken;
var context = ResilienceContext.Get(cancellationToken);

try
{
Expand Down
43 changes: 41 additions & 2 deletions src/Polly.Core/ResilienceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Polly;
/// </summary>
/// <remarks>
/// Do not re-use an instance of <see cref="ResilienceContext"/> across more than one execution. The <see cref="ResilienceContext"/> is retrieved from the pool
/// 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.
/// 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.
/// </remarks>
public sealed class ResilienceContext
{
Expand All @@ -23,6 +23,19 @@ private ResilienceContext()
{
}

/// <summary>
/// Gets a key unique to the call site of the current execution.
/// </summary>
/// <remarks>
/// Resilience strategy instances are commonly reused across multiple call sites.
/// Set an <see cref="OperationKey"/> 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 <see cref="Guid"/> to this property).
/// <para>
/// Defaults to <see langword="null"/>.
/// </para>
/// </remarks>
public string? OperationKey { get; private set; }

/// <summary>
/// Gets or sets the <see cref="CancellationToken"/> associated with the execution.
/// </summary>
Expand Down Expand Up @@ -69,15 +82,40 @@ private ResilienceContext()
/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
public static ResilienceContext Get(CancellationToken cancellationToken = default)
{
var context = Pool.Get();
context.CancellationToken = cancellationToken;
return context;
}

/// <summary>
/// Gets a <see cref="ResilienceContext"/> instance from the pool.
/// </summary>
/// <param name="operationKey">An operation key associated with the context.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An instance of <see cref="ResilienceContext"/>.</returns>
/// <remarks>
/// After the execution is finished you should return the <see cref="ResilienceContext"/> back to the pool
/// by calling <see cref="Return(ResilienceContext)"/> method.
/// </remarks>
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;
Expand Down Expand Up @@ -121,6 +159,7 @@ internal void AddResilienceEvent(ResilienceEvent @event)

internal bool Reset()
{
OperationKey = null;
IsSynchronous = false;
ResultType = typeof(UnknownResult);
ContinueOnCapturedContext = false;
Expand Down
3 changes: 1 addition & 2 deletions src/Polly.Core/ResilienceStrategy.Async.ValueTaskT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ static async (context, state) =>

private static ResilienceContext GetAsyncContext<TResult>(CancellationToken cancellationToken)
{
var context = ResilienceContext.Get();
context.CancellationToken = cancellationToken;
var context = ResilienceContext.Get(cancellationToken);

InitializeAsyncContext<TResult>(context);

Expand Down
3 changes: 1 addition & 2 deletions src/Polly.Core/ResilienceStrategy.SyncT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ public TResult Execute<TResult, TState>(

private static ResilienceContext GetSyncContext<TResult>(CancellationToken cancellationToken)
{
var context = ResilienceContext.Get();
context.CancellationToken = cancellationToken;
var context = ResilienceContext.Get(cancellationToken);

InitializeSyncContext<TResult>(context);

Expand Down
8 changes: 8 additions & 0 deletions src/Polly.Extensions/Telemetry/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -26,6 +27,7 @@ public static partial void ResilienceEvent(
string? strategyName,
string strategyType,
string? strategyKey,
string? operationKey,
object? result,
Exception? exception);

Expand All @@ -35,19 +37,22 @@ 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(
EventId = 2,
Message = "Resilience strategy executed. " +
"Builder Name: '{BuilderName}', " +
"Strategy Key: '{StrategyKey}', " +
"Operation Key: '{OperationKey}', " +
"Result Type: '{ResultType}', " +
"Result: '{Result}', " +
"Execution Health: '{ExecutionHealth}', " +
Expand All @@ -58,6 +63,7 @@ public static partial void StrategyExecuted(
LogLevel logLevel,
string? builderName,
string? strategyKey,
string? operationKey,
string resultType,
object? result,
string executionHealth,
Expand All @@ -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}', " +
Expand All @@ -84,6 +91,7 @@ public static partial void ExecutionAttempt(
string? strategyName,
string strategyType,
string? strategyKey,
string? operationKey,
object? result,
bool handled,
int attempt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -112,6 +113,7 @@ private void LogEvent(TelemetryEventArguments args)
args.Source.StrategyName,
args.Source.StrategyType,
strategyKey,
args.Context.OperationKey,
result,
executionAttempt.Handled,
executionAttempt.Attempt,
Expand All @@ -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);
}
}
}
3 changes: 2 additions & 1 deletion src/Polly.Extensions/Telemetry/ResilienceTelemetryTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ 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";

public const string AttemptNumber = "attempt-number";

public const string AttemptHandled = "attempt-handled";

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, T
TState state)
{
var stamp = _timeProvider.GetTimestamp();
Log.ExecutingStrategy(_logger, _builderName, _strategyKey, context.GetResultType());
Log.ExecutingStrategy(_logger, _builderName, _strategyKey, context.OperationKey, context.GetResultType());

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

Expand All @@ -63,6 +63,7 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCoreAsync<TResult, T
logLevel,
_builderName,
_strategyKey,
context.OperationKey,
context.GetResultType(),
ExpandOutcome(context, outcome),
context.GetExecutionHealth(),
Expand All @@ -88,6 +89,7 @@ private void RecordDuration<TResult>(ResilienceContext context, Outcome<TResult>
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()));
Expand Down
3 changes: 1 addition & 2 deletions src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ public static ResilienceContext Create(
bool continueOnCapturedContext,
out IDictionary<string, object> oldProperties)
{
var resilienceContext = ResilienceContext.Get();
resilienceContext.CancellationToken = cancellationToken;
var resilienceContext = ResilienceContext.Get(context.OperationKey, cancellationToken);
resilienceContext.ContinueOnCapturedContext = continueOnCapturedContext;
resilienceContext.Properties.SetProperties(context, out oldProperties);

Expand Down
27 changes: 26 additions & 1 deletion test/Polly.Core.Tests/ResilienceContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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<bool>(synchronous);
context.ContinueOnCapturedContext = true;

Expand All @@ -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)]
Expand Down Expand Up @@ -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();
}
}
Loading

0 comments on commit a806318

Please sign in to comment.