/// <summary> /// Generates a Polly <see cref="FallbackPolicy{HttpResponseMessage}"/> from the configuration. /// </summary> /// <param name="logger">The <see cref="ILogger"/> instance to use for logging.</param> /// <returns>A <see cref="FallbackPolicy{HttpResponseMessage}"/> instance.</returns> public IAsyncPolicy <HttpResponseMessage> AsTypeModel(ILogger logger) { _ = logger ?? throw new ArgumentNullException(nameof(logger)); if (Status < 0) { logger.LogCritical("{PolicyConfig} : {Property} is negative", nameof(FallbackConfig), "statusCode"); throw new InvalidOperationException("statusCode cannot be negative"); } var message = new HttpResponseMessage { StatusCode = (HttpStatusCode)Status, ReasonPhrase = Reason, Content = new StringContent(Content ?? string.Empty, Encoding.UTF8, "application/json") }; logger.LogDebug("Created fallback mesage {StatusCode} - {ReasonPhrase} - {Content}", message.StatusCode, message.ReasonPhrase, message.Content); return(Policy.HandleHttpRequests() .FallbackAsync(message, onFallbackAsync: async(result, context) => { logger.LogWarning(result.Exception, "{PolicyKey} at {OperationKey}: fallback value substituted", context.PolicyKey, context.OperationKey); await Task.CompletedTask; })); }
/// <summary> /// Generates a Polly <see cref="CircuitBreakerPolicy{HttpResponseMessage}"/> from the configuration. /// </summary> /// <param name="logger">The <see cref="ILogger"/> instance to use for logging.</param> /// <returns>A <see cref="CircuitBreakerPolicy{HttpResponseMessage}"/> instance.</returns> public IAsyncPolicy <HttpResponseMessage> AsTypeModel(ILogger logger) { _ = logger ?? throw new ArgumentNullException(nameof(logger)); if (BreakDuration < 0.02d) { logger.LogCritical("{PolicyConfig} : {Property} must be greater than 20ms", nameof(AdvancedCircuitBreakerConfig), "breakDuration"); throw new InvalidOperationException("breakDuration must be greater than 20ms"); } if (FailureThreshold < 0.0d || FailureThreshold > 1.0d) { logger.LogCritical("{PolicyConfig} : {Property} must be between 0 and 1", nameof(AdvancedCircuitBreakerConfig), "threshold"); throw new InvalidOperationException("threshold must be between 0 and 1"); } if (SamplingDuration < 0.02d) { logger.LogCritical("{PolicyConfig} : {Property} must be greater than 20ms", nameof(AdvancedCircuitBreakerConfig), "samplingDuration"); throw new InvalidOperationException("samplingDuration must be greater than 20ms"); } if (MinimumThroughput < 0) { logger.LogCritical("{PolicyConfig} : {Property} is negative", nameof(AdvancedCircuitBreakerConfig), "throughput"); throw new InvalidOperationException("throughput cannot be negative"); } // Create delegates void OnBreak(DelegateResult <HttpResponseMessage> result, TimeSpan timespan, Context context) { logger.LogError(result.Exception, "{PolicyKey} at {OperationKey}: {CircuitState} triggered for {CircuitDelay} seconds due to {StatusCode}", context.PolicyKey, context.OperationKey, CircuitState.Open, timespan.TotalSeconds, result.Result?.StatusCode); } void OnReset(Context context) { logger.LogInformation("{PolicyKey} at {OperationKey}: {CircuitState} triggered", context.PolicyKey, context.OperationKey, CircuitState.Closed); } void OnHalfOpen() => logger.LogDebug("{CircuitState} triggered", CircuitState.HalfOpen); // Create policy var breaker = Policy .HandleHttpRequests() .AdvancedCircuitBreakerAsync( failureThreshold: FailureThreshold, samplingDuration: TimeSpan.FromSeconds(SamplingDuration), minimumThroughput: MinimumThroughput, durationOfBreak: TimeSpan.FromSeconds(BreakDuration), onBreak: OnBreak, onReset: OnReset, onHalfOpen: OnHalfOpen); return(breaker); }
/// <summary> /// Generates a Polly <see cref="CircuitBreakerPolicy{HttpResponseMessage}"/> from the configuration. /// </summary> /// <param name="logger">The <see cref="ILogger"/> instance to use for logging.</param> /// <returns>A <see cref="CircuitBreakerPolicy{HttpResponseMessage}"/> instance.</returns> public IAsyncPolicy <HttpResponseMessage> AsTypeModel(ILogger logger) { _ = logger ?? throw new ArgumentNullException(nameof(logger)); if (BreakDuration < 0.02d) { logger.LogCritical("{PolicyConfig} : {Property} must be greater than 20ms", nameof(CircuitBreakerConfig), "duration"); throw new InvalidOperationException("duration must be greater than 20ms"); } if (FaultTolerance < 1) { logger.LogCritical("{PolicyConfig} : {Property} must be greater than 0", nameof(CircuitBreakerConfig), "tolerance"); throw new InvalidOperationException("tolerance must be greater than 0"); } // Create delegates void OnBreak(DelegateResult <HttpResponseMessage> result, TimeSpan timespan, Context context) { logger.LogError(result.Exception, "{PolicyKey} at {OperationKey}: {CircuitState} triggered for {CircuitDelay} seconds due to {StatusCode}", context.PolicyKey, context.OperationKey, CircuitState.Open, timespan.TotalSeconds, result.Result?.StatusCode); } void OnReset(Context context) { logger.LogInformation("{PolicyKey} at {OperationKey}: {CircuitState} triggered", context.PolicyKey, context.OperationKey, CircuitState.Closed); } void OnHalfOpen() => logger.LogDebug("{CircuitState} triggered", CircuitState.HalfOpen); // Create policy var breaker = Policy .HandleHttpRequests() .CircuitBreakerAsync( handledEventsAllowedBeforeBreaking: FaultTolerance, durationOfBreak: TimeSpan.FromSeconds(BreakDuration), onBreak: OnBreak, onReset: OnReset, onHalfOpen: OnHalfOpen ); return(breaker); }
/// <summary> /// Generates a Polly <see cref="RetryPolicy{HttpResponseMessage}"/> from the configuration. /// </summary> /// <param name="logger">The <see cref="ILogger"/> instance to use for logging.</param> /// <returns>A <see cref="RetryPolicy{HttpResponseMessage}"/> instance.</returns> public IAsyncPolicy <HttpResponseMessage> AsTypeModel(ILogger logger) { _ = logger ?? throw new ArgumentNullException(nameof(logger)); if (DelaysInSeconds is null) { logger.LogCritical("{PolicyConfig} : {Property} is not initialized", nameof(RetryConfig), "delays"); throw new InvalidOperationException("delays cannot be null"); } if (JitterMilliseconds < 0) { logger.LogCritical("{PolicyConfig} : {Property} is negative", nameof(RetryConfig), "jitter"); throw new InvalidOperationException("jitter cannot be negative"); } var builder = Policy.HandleHttpRequests(); List <double> delays = DelaysInSeconds.ToList(); bool forever = Retries < 1; bool immediate = !delays.Any(); if (immediate) { if (forever) { return(builder.RetryForeverAsync(onRetry: result => logger.LogError(result.Exception, "{RetryPolicy} failed with {StatusCode}", "RetryForeverAsync", result.Result?.StatusCode))); } else { return(builder.RetryAsync(Retries, onRetry: (result, count) => logger.LogError(result.Exception, "{RetryPolicy} failed with {StatusCode} on attempt {RetryCount}", "RetryAsync", result.Result?.StatusCode, count))); } } bool exponential = delays.Count == 1 && delays.First() == -1.0d; // Defined delays rely on the list being initialized with positive delay values if (!exponential && delays.Any(d => d < 0)) { throw new InvalidOperationException($"delay values cannot be negative"); } if (forever) { return(builder.WaitAndRetryForeverAsync( sleepDurationProvider: count => Delay(count, exponential), onRetry: (result, timespan) => logger.LogError(result.Exception, "{RetryPolicy} failed with {StatusCode} after {RetryTime} seconds", "WaitAndRetryForeverAsync", result.Result?.StatusCode, timespan.TotalSeconds))); } else { return(builder.WaitAndRetryAsync( Retries, sleepDurationProvider: count => Delay(count, exponential), onRetry: (result, timespan, count, context) => logger.LogError(result.Exception, "{RetryPolicy} failed with {StatusCode} on attempt {RetryCount} after {RetryTime} seconds", "WaitAndRetryAsync", result.Result?.StatusCode, count, timespan.TotalSeconds))); } }