Reputation: 3328
When making HTTP calls using an instance of HttpClient
wrapped in Polly's retry policy and injected into a controller using dependency injection, I want to send ILogger<T>
from the call site to a delegate of the retry policy (e.g., onRetry
), so logs are registered more appropriately.
Polly docs explain how to achieve this by sending ILogger<T>
from the calls site to the retry delegates leveraging Context
encapsulated in an HttpRequestMessage
request.
However, this solution works when you leverage a method of the HttpClient
that takes HttpRequestMessage
in one of its overloads. For instance, client.SendAsync
.
However, not every method of HttpClient
take HttpRequestMessage
. For instance, I'm using client.GetStreamAsync
, which none of its overloads take HttpRequestMessage
.
In this case, I wonder how you would pass the Ilogger<T>
to Polly's retry delegates.
Upvotes: 1
Views: 1379
Reputation: 22829
Context
object with HttpRequestMessage
As you have stated in your question this is not applicable, since you don't have a HttpRequestMessage
instance on which you could access the Context
via the request.GetPolicyExecutionContext
call.
AddPolicyHandler
+ IServiceProvider
The AddPolicyHandler
has an overload which provides access to the IServiceProvider
and to the HttpRequestMessage
. You could obtain an ILoggerFactory
via provider.GetRequiredService<ILoggerFactory>()
and then you could call factory.CreateLogger<T>
.
The problem with this approach is that you don't know T
at policy registration time, since you want to use the Controller as T
.
If you would define the policy inside the same class where you have the intention to use it then you could access the ILogger<YourController>
.
There are two drawbacks of this approach:
ExecuteAsync
The first issue can be addressed via the PolicyRegistry
PolicyRegistry
and using Context
You can register your policy/ies into a PolicyRegistry
and then you can obtain them (via IReadOnlyPolicyRegistry
) inside your controller. This approach lets you define your policy in the way that you can retrieve an ILogger
from the Context
inside the onRetry
. And you can specify the Context
when you call the ExecuteAsync
var context = new Polly.Context().WithLogger(yourControllerLogger);
await policy.ExecuteAsync(async (ct) => ..., context);
PolicyRegistry
and using try
-catch
The previous approach used the Context
to transfer an object between the policy definition and its usage. One can say that this separation is a bit fragile since the coupling between these two is not explicit rather via a magic Context
object.
An alternative solution could be to perform logging only inside your the ExecuteAsync
to avoid the usage of the Context
await policy.ExecuteAsync(async () =>
try
{
...
}
catch(Exception ex) //filter for the retry's trigger exception(s)
{
yourControllerLogger.LogError(...);
});
As you can see none of the above solutions is perfect since you want to couple the policy and its usage via logging.
UPDATE #1
I'm not a big fan of defining policy inside a controller, because I generally reuse a policy (and accordingly the
HttpClientFactory
) in different controllers.
As I said above, this is one option out of three. The other two options do not require you to define your policy inside the controller class. You can define them inside the startup
var registry = new PolicyRegistry()
{
{ "YourPolicyName", resilientStrategy }
};
services.AddPolicyRegistry(registry);
and then retrieve the given policy inside the controller
private readonly IAsyncPolicy policy;
public YourController(IReadOnlyPolicyRegistry<string> registry)
{
policy = registry.Get<IAsyncPolicy>("YourPolicyName"):
}
I suppose there is no other cleaner solution
If you want to / need to use the controller's logger inside the onRetry
delegate then I'm unaware of any cleaner solution.
If you want to use that logger to be able to correlate the controller's log with the policy's log then I would rather suggest to use a correlation id per request and include that into your logs. Steve Gordon has a nuget package called correlationId which can help you to achieve that.
Upvotes: 2