In a previous post, I looked at how we could use the Polly context to obtain a retry count after policy execution. In this post, I want to explore a similar requirement that can also be solved by using the Polly context; this time to pass in an ILogger to be used for logging messages while executing a policy.
A common and reasonable scenario when executing Polly policies is to require logging of what is happening within those policies. In a retry policy, for example, I commonly want to log a warning message when a retry occurs. This indicates that there may be a problem with a downstream system. While a retry attempt is not a complete failure at this point since by definition we’re retrying in the hope that the downstream system has recovered, it is useful to be aware of the fact that a retry was required. Polly provides a number of hooks with the various policies which allow you to pass in delegate code to be executed. In the case of the retry policy, we can pass a delegate for the onRetry action. Each policy has its own applicable delegate methods which are raised when various situations occur.
Typically, I like to pass in a logger that has the context of where the original execution of the policy occurred, the class which triggered the execution of the policy. In ASP.NET Core, accessing a logger instance for your services is as easy as requesting an ILogger<T> to be injected into your constructor, where T is the type of your class. This ILogger is configured with the correct category name for your class. Using an ILogger instance which comes from the calling code makes sense to me since my logs can then be filtered for all messages from that class when tracking down failures.
Let’s explore how we can easily pass the ILogger through to the policy using the execution context. The snippets for this post can be found in a complete sample repository on GitHub.
Setting up the Context
We’ll start with the code used to execute the policy which is pretty standard code. The example below is an ASP.NET Core Action method that triggers a call to an external service, wrapped in a retry policy.
We’re using the HttpClientFactory feature to access a HttpClient instance in line 6. If you are not familiar with HttpClientFactory, you can read more about the feature in my HttpClientFactory series.
Next, we use the Polly policy registry to access a stored retry policy.
In line 10 of the preceding code, we create our Polly context object. It’s this context that we’ll use to pass a reference to the ILogger for our Controller class, into the policy being executed. The context is a wrapper over a Dictionary<string, object>, so we can add an object to the context using a string as the key. I prefer to avoid magic strings in my code so, in this sample, we have created a static class, defining some constant keys that will be used for the objects we add to the context.
Now that we have a context prepared, we can pass it to the execution of the policy as shown on line 16 above, as a parameter on the ExecuteAsync method.
Next, we’ll take a look at the definition for the retry policy, to see how it makes use of the ILogger from the context. Since this sample uses a policy registry, we have defined an extension method on the registry in order to create and register our retry policy.
Much of this code will be familiar if you’ve worked with Polly previously. If you haven’t, a great place to learn more is on the Polly wiki.
The section I want to focus on is the code for the onRetry Action, which is the third parameter on the WaitAndRetryAsync method on line 6.
Here we’re using the overload which allows us to pass a delegate that has access to the DelegateResult<HttpResponceMessage>, the retry TimeSpan, the current retry count and most importantly, the Polly Context which was passed via the ExecuteAsync method.
Here, we have defined a Lambda to operate on these available objects.
On line 8 in the preceding code, we use an extension method defined on the Polly context, which tries to retrieve an ILogger from the context using our constant key. If this returns false, which indicates that the logger was not found, we simply return from this delegate.
For reference, the code for the extension method is as follows:
Going back to our onRetry Action code in our policy definition, if we do find an entry for an ILogger, we use it to log an appropriate error message.
That’s about it for this short blog post which I hope has demonstrated the power of using the context to pass in objects that can be used when the policy executes. We added the ILogger as an item in a new context, passed the context to the policy at the point of execution and finally, within our policy, accessed the context within the delegate action and used the ILogger contained within it.