记录异常并重新抛出的 Polly 策略

Polly policy to log exception and rethrow

我考虑使用 Polly 创建策略来记录异常并重新抛出。 我没有找到允许开箱即用的现有方法,但我看到的一些选项是

回退

// Specify a substitute value or func, calling an action (e.g. for logging)
// if the fallback is invoked.
Policy.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank,
    onFallback: (exception, context) =>
    {
        _logger.Log(exception, context);
        throw exception;
    });

问题:是否可以从 Fallback 中抛出异常?

超时

Policy.Timeout(1, T30meoutStrategy.Pessimistic,
(context, timespan, task) =>
{
    // ContinueWith important!: the abandoned task may very well still be executing,
    // when the caller times out on waiting for it!
    task.ContinueWith(t =>
    {
        if (t.IsFaulted)
        {
            logger.Error(context,t.Exception);
            throw exception;
        }
    });
}

重试

Policy.Handle<DivideByZeroException>().Retry(0,
(exception, retryCount) =>
{
    logger.Error(context,exception);
    throw exception;
});

问题:是否支持0次重试?

或者亲亲写简单的try/catch自己扔

这些方法哪个更好? 你有什么建议?

如果您还没有 Polly,try/catch 看起来最简单。

如果您已经混合使用 Polly,FallbackPolicy can safely be re-purposed in the way you suggest. The onFallback delegate and fallback action or value are not governed by the .Handle<>() clauses of the Policy,那么您可以安全地从 onFallback 委托中重新抛出异常。

Policy<UserAvatar>.Handle<Whatever>()
.Fallback<UserAvatar>(UserAvatar.Blank,
    onFallback: (exception, context) =>
    {
        _logger.Log(exception, context);
        throw exception;
    });

你的问题用 TimeoutPolicy 概述的方法只会捕获调用者早些时候由于超时而离开的委托抛出的异常,并且只在 TimeoutMode.Pessimistic 中;并非所有例外。


您的问题用 .Retry(0, ...) 概述的方法是行不通的。如果未指定重试,则不会调用 onRetry 委托。


为了避免重新利用 FallbackPolicy 带来的麻烦,您也可以在 Polly 的结构中编写自己的代码 LogThenRethrowPolicyThis commit (which added the simple NoOpPolicy) 举例说明了添加新策略所需的最低限度。您可以添加类似于 NoOpPolicy 的实现,但只是 try { } catch { /* log; rethrow */ }


2019 年 1 月编辑:Polly.Contrib 现在还包含一个 Polly.Contrib.LoggingPolicy 可以帮助解决这个问题。

https://github.com/App-vNext/Polly-Samples/blob/master/PollyDemos/Async/AsyncDemo02_WaitAndRetryNTimes.cs 表明您可以使用 onRetry: 选项,至少对于 WaitAndRetryAsync。其他的我还没看呢

HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3,
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))  // exponential back-off: 2, 4, 8 etc
                    + TimeSpan.FromMilliseconds(Jitterer.Next(0, 1000)), // plus some jitter: up to 1 second
    onRetry: (response, calculatedWaitDuration) =>
    {
        logger.LogError($"Failed attempt. Waited for {calculatedWaitDuration}. Retrying. {response.Exception.Message} - {response.Exception.StackTrace}");
    }
);

这是我的通用方法解决方案

public async Task<T> PollyRetry<T>(
        Func<Task<T>> action)
    {

        bool hasFallback = false;
        Exception ex = null;

        var fallbackPolicy = Policy<T>.Handle<Exception>().FallbackAsync(
            default(T), d =>
            {
                //log final exception

                ex = d.Exception;

                hasFallback = true;
                return Task.FromResult(new { });

            });

        var retryPolicy = Policy
            .Handle<Exception>()
            .WaitAndRetryAsync(3,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                (res, timeSpan, context) =>
                {
                    //log exception
                });

        var policyResult = await fallbackPolicy.WrapAsync(retryPolicy).ExecuteAndCaptureAsync(action);

        if (hasFallback && ex != null)
            throw ex;

        return policyResult.Result;
    }

//call service with retry logic
        TestResponse response = await _pollyRetryService.PollyRetry(async () =>
        {
            return await _testService.Test(input);

        });