Reputation: 387
Or: How to log from a static method.
From https://github.com/App-vNext/Polly you have examples like this one where a logger is magically available:
Policy
.Timeout(30, onTimeout: (context, timespan, task) =>
{
logger.Warn($"{context.PolicyKey} at {context.ExecutionKey}: execution timed out after {timespan.TotalSeconds} seconds.");
});
In my code I am using the new IHttpClientFactory
pattern from dotnet core 2.1, and adding it like this in my Startup.cs ConfigureServices
method:
services.AddHttpClient<IMySuperHttpClient, MySuperHttpClient>()
.AddPolicyHandler(MySuperHttpClient.GetRetryPolicy())
.AddPolicyHandler(MySuperHttpClient.GetCircuitBreakerPolicy());
With the GetRetryPolicy
being static and looking like this:
internal static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(
retryCount: 4,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: OnRetry);
}
Where the OnRetry
method aswell have to be static:
private static void OnRetry(DelegateResult<HttpResponseMessage> delegateResult, TimeSpan timespan, Context context)
{
// var logger = ??
// logger.LogWarning($"API call failed blah blah.");
}
How can you access an ILoggerFactory
here, if at all possible?
Upvotes: 13
Views: 7444
Reputation: 22829
As it was stated in the comments by mountain traveller there are multiple ways to solve the issue:
Context
to pass any arbitrary object between the Execute(Async)
and onRetry(Async)
delegateAddPolicyHandler
The former solution requires an explicit call of Execute
/ ExecuteAsync
to be able to provide a Context
object which encapsulates an ILogger
instance. In case of Named/Typed Http Client this route is not a viable option, since you decorate the whole HttpClient
with a PolicyHttpMessageHandler
. So, not your code is the one which calls the Execute(Async)
rather the Handler on your behalf.
The latter solution provides access to the IServiceProvider
from which you can retrieve any registered service. (It also passes the request as a parameter.)
services.AddHttpClient<IMySuperHttpClient, MySuperHttpClient>()
.AddPolicyHandler((provider, _) => MySuperHttpClient.GetRetryPolicy(provider))
So, all you need to do is to pass through the IServiceProvider
down to your OnRetry
method
internal static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(IServiceProvider provider)
=> HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(4,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
OnRetry(provider));
}
There is no such overload of WaitAndRetryAsync
where the onRetry
delegate anticipates an IServiceProvider
. We need to introduce a helper method like this:
private static Action<DelegateResult<HttpResponseMessage>, TimeSpan> OnRetry(IServiceProvider provider)
=> (dr, ts) => OnRetry(dr, ts, provider);
private static void OnRetry(DelegateResult<HttpResponseMessage> delegateResult, TimeSpan timespan, IServiceProvider provider)
{
// var logger = ??
// logger.LogWarning($"API call failed blah blah.");
}
OnRetry
receives an IServiceProvider
and returns an Action<DelegateResult<HttpResponseMessage>, TimeSpan>
WaitAndRetryAsync
's onRetry
OnRetry
is the same yours, I've just replaced your context parameter to the providerSo, basically the first OnRetry
acts as an adapter between the WaitAndRetryAsync
and the second OnRetry
to pass through the provider
.
Upvotes: 9