Keys and Context Data - App-vNext/Polly GitHub Wiki
âšī¸ This documentation describes the previous Polly v7 API. If you are using the new v8 API, please refer to pollydocs.org.
An execution-scoped instance of the Polly.Context
class travels with every execution through a Polly policy.
The role of this class is to provide execution context and to allow the exchange of information between the pre-execution, mid-execution, and post-execution phases.
Context
carries two kinds of information:
- Values for pre-defined keys - these can uniquely identify the policy in use, policywrap in use, execution call site, and a unique correlation-id per execution.
-
Custom data - you can add any custom data to
Context
, using Dictionary<string, object> semantics.
The context
instance travelling with the execution is available:
- before execution (if you define it yourself and pass it in)
- to the main delegate
.Execute/Async(...)
-d through the policy (when you use an overload takingContext
) - to every delegate hook (
onRetry
,onBreak
etc) - after execution.
It can therefore be used to pass information into executions, get information out of executions, and exchange information among any part of a policy execution.
The following four pre-defined keys are available as properties directly on Context
:
Key | Scope (ie unique to) | How to set | Default (if not set) |
Purpose |
---|---|---|---|---|
PolicyWrapKey |
PolicyWrap instance (in nested wrap, takes outermost specified value) |
policyWrap .WithPolicyKey("someValue")
|
PolicyWrap-partguid; or null if no PolicyWrap in use |
Indicate the PolicyWrap in use |
PolicyKey |
Policy instance |
policy .WithPolicyKey("someValue")
|
PolicyType-partguid | Indicate the Policy in use |
OperationKey |
call site (if code specifies it) |
pass new Context("someOperationKey") into the Execute call |
null |
Indicate where the policy in use Used by CachePolicy as the cache key for the execution |
CorrelationId |
an individual execution | not user-settable | a unique guid | Uniquely correlate logs/ metrics for a single execution |
OperationKey
was named ExecutionKey
prior to v6.
CorrelationId
was named ExecutionGuid
prior to v6.
// Identify policies with a PolicyKey, using the WithPolicyKey() extension method
// (for example, for correlation in logs or metrics)
var policy = Policy
.Handle<DataAccessException>()
.Retry(3, onRetry: (exception, retryCount, context) =>
{
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}.");
})
.WithPolicyKey("MyDataAccessPolicy");
// Identify call sites with an OperationKey, by passing in a Context
var customerDetails = policy.Execute(myDelegate, new Context("GetCustomerDetails"));
// "MyDataAccessPolicy" -> context.PolicyKey
// "GetCustomerDetails" -> context.OperationKey
Context
has full Dictionary<string, object>
semantics. You can set data on an instance of Context
var context = new Polly.Context();
context["MyCustomData"] = foo;
and get it back later anywhere context
is available - within an onRetry
hook, within the delegate executed through the policy, after policy execution:
var myFooFromElsewhere = context["MyCustomData"];
Given a policy (abbreviated):
// Policy which logs information available from both pre-defined and custom user context data
var policy = Policy
.Handle<FooException>()
.RetryAsync(3, onRetry: (exception, retryCount, context) =>
{
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, getting {context["Type"]} of id {context["Id"]}, due to: {exception}. CorrelationId: {context.CorrelationId}");
})
.WithPolicyKey("GetEntitiesPolicy");
An example call site could pass in extra information (abbreviated example code):
public class EntityDataAccess<T>
{
private IAsyncPolicy Policy { get; }
public EntityDataAccess(IAsyncPolicy policy) { Policy = policy; }
public async T GetAsync(int id)
{
var context = new Context($"GetAsync-{typeof(T).Name}-{Id}", new Dictionary<string, object>() {
{ "Type" , typeof(T).Name },
{ "Id" , id }
});
return await Policy.ExecuteAsync(ctx => database.GetAsync(id), context);
}
}
A common use of Context
is to bridge the fact that policies are often defined (including policy callback delegates) on startup; but some information you want to use within the delegate hooks (such as an ILogger
) might only be available via dependency injection at the call site.
The policy can be pre-defined in StartUp
to retrieve the information from context
:
var policy = Policy.Handle<FooException>()
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, retryCount, context) =>
{
var msg = $"Retrying request for {retryCount} " +
$"at {context.OperationKey}, " +
$"due to: {exception}.";
var logger = context.GetLogger();
logger?.LogWarning(msg);
});
And the call site can configure the ILogger
onto Context
prior to execution:
// Meanwhile at the call site, given some ILogger logger available by dependency injection
Context context = new Context().WithLogger(logger);
return await policy.ExecuteAsync(ctx => FooAsync(), context);
In the above code, we used some helper methods to make the storage and retrieval of the ILogger
more concise and robust:
public static class ContextExtensions
{
private static readonly string LoggerKey = "LoggerKey";
public static Context WithLogger(this Context context, ILogger logger)
{
context[LoggerKey] = logger;
return context;
}
public static ILogger GetLogger(this Context context)
{
if (context.TryGetValue(LoggerKey, out object logger))
{
return logger as ILogger;
}
return null;
}
}
See the Polly with HttpClientFactory page.