In C# I'm using Polly Version=8.4.1 and a reusable retry ResiliencePipeline via ResiliencePipelineBuilder.
return new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5,
DelayGenerator = {blah(blah, blah);},
ShouldHandle = args => { blahblah(blah); }
}).Build();
I want to be able to add logging for when the operation initially fails but subsequently succeeds. Something like:
{operations} recovered after 3 tries, in 22 seconds
I believe in older versions of Polly this would be accomplished by injecting handling via the OnSuccess operation.
How is this done in v8.x?
On way to solve this problem is by introducing a custom ResilienceStrategy
. This strategy wraps your retry strategy and captures the execution time via a simple Stopwatch
. It also retrieves the attempt count via the usage of ResilienceContext
.
Here you can find a working example: https://dotnetfiddle.net/9uEFbl.
public sealed class RetryReporterStrategy: ResilienceStrategy
{
private readonly ResiliencePropertyKey<int> _attemptCount;
public RetryReporterStrategy(ResiliencePropertyKey<int> attemptCount)
{
_attemptCount = attemptCount;
}
protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
var watch = Stopwatch.StartNew();
var result = await callback(context, state).ConfigureAwait(context.ContinueOnCapturedContext);
watch.Stop();
context.Properties.TryGetValue(_attemptCount, out var attempts);
if (result.Exception is not null)
Console.WriteLine($"The operation failed after {attempts+1} tries, in {watch.Elapsed.TotalSeconds} seconds");
else
Console.WriteLine($"The operation succeeded after {attempts+1} tries, in {watch.Elapsed.TotalSeconds} seconds");
return result;
}
}
ResiliencePropertyKey
which is used to set & get the attempt number on the context
ExecuteCore
starts the stopwatch, executes the wrapped strategy (var result = await callback(...
)` and stops the stopwatchTryGetValue
returns true
before you start using the attempts
variableresult
it performs some logging
public static class RetryExtensions
{
public static ResiliencePipelineBuilder AddFancyRetry(this ResiliencePipelineBuilder builder, RetryStrategyOptions options)
{
ResiliencePropertyKey<int> attemptCountKey = new(nameof(attemptCountKey));
return builder
.AddStrategy(context => new RetryReporterStrategy(attemptCountKey), options)
.AddRetry(new() {
BackoffType = options.BackoffType,
Delay = options.Delay,
DelayGenerator = options.DelayGenerator,
MaxDelay = options.MaxDelay,
MaxRetryAttempts = options.MaxRetryAttempts,
Name = options.Name,
Randomizer = options.Randomizer,
OnRetry = args =>
{
args.Context.Properties.Set(attemptCountKey, args.AttemptNumber);
return options.OnRetry?.Invoke(args) ?? default;
},
ShouldHandle = options.ShouldHandle,
UseJitter = options.UseJitter
});
}
}
ResiliencePipelineBuilder
to ease the usage
ResiliencePipelineBuilder<TResult>
typeResiliencePropertyKey
which is used for communication (via the context) between our custom strategy and the retryRetryReporterStrategy
first as the outer strategy into the pipeline (.AddStrategy
).AddRetry
)
options
parameter, except for OnRetry
OnRetry
delegate if it was providedvar pipeline = new ResiliencePipelineBuilder()
.AddFancyRetry(new () {
ShouldHandle = args => PredicateResult.True(),
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
})
.Build();
await pipeline.ExecuteAsync(async ct => await Task.Delay(100, ct), CancellationToken.None);
The output
The operation succeeded after 3 tries, in 3.4203751 seconds
Task.Delay
have been executed four times >> 0.4s
pipeline = new ResiliencePipelineBuilder()
.AddFancyRetry(new () {
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
MaxRetryAttempts = 5,
Delay = TimeSpan.FromMilliseconds(500),
OnRetry = static args =>
{
Console.WriteLine("OnRetry");
return default;
}
})
.Build();
await pipeline.ExecuteAsync(async ct =>
{
await Task.Delay(100, ct);
throw new Exception();
}, CancellationToken.None);
The output
OnRetry
OnRetry
OnRetry
OnRetry
OnRetry
The operation failed after 5 tries, in 3.1136394 seconds
Unhandled exception. System.Exception: Exception of type 'System.Exception' was thrown.
In this scenario we had