Search code examples
c#pollyretry-logic

Why is OperationKey empty in my Polly retry policy?


I'm using Polly version 7.1.1

I have a simple Polly retry policy that will retry an operation once if it fails with an NpgsqlException:

var policy = Policy
    .Handle<NpgsqlException>()
    .Retry(
        retryCount: 1,
        onRetry: (exception, retryCount, context) =>
        {
            _logger.Log(LogLevel.Warning, $"Retry {retryCount} of {context.OperationKey}, due to {exception.Message}", exception);
        })
    .WithPolicyKey("PostgresConnectionPolicy");

I have a method that attempts to connect to a PostgreSQL database and run a query using said policy:

using (var conn = new NpgsqlConnection("myConnectionString"))
{
    conn.Open()

    using(var command = GetCommand())
    {
        policy.Execute(
            action: context => command.ExecuteNonQuery(),
            contextData: new Context("Command.ExecuteNonQuery"));
    }
}

The method fails, gets retried, and the logger prints the following:

Retry 1 of , due to 42P01: relation "myDatabase" does not exist

I would expect the following to be logged:

Retry 1 of Command.ExecuteNonQuery, due to 42P01: relation "myDatabase" does not exist

Why is OperationKey empty when the policy logs the retry?

EDIT: Adding a simplified console application as an example.

This prints the following to the console:

Retry 1 of , due to Testing Polly
Error.

Example:

using Polly;
using System;

namespace TestPolly
{
    class Program
    {
        static void Main(string[] args)
        {
            var policy = Policy
                .Handle<Exception>()
                .Retry(
                    retryCount: 1,
                    onRetry: (exception, retryCount, context) =>
                    {
                        Console.WriteLine($"Retry {retryCount} of {context.OperationKey}, due to {exception.Message}");
                    })
                .WithPolicyKey("PostgresConnectionPolicy");

            try
            {
                policy.Execute(
                    action: context => TestPolly(),
                    contextData: new Context("TestPolly"));
            }
            catch (Exception e)
            {
                Console.WriteLine("Error.");
            }

            Console.ReadKey();
        }

        private static void TestPolly()
        {
            throw new Exception("Testing Polly");
        }
    }
}

Solution

  • The Context object

    • defines some predefined fields (like OperationKey, PolicyKey, CorrelationId)
    • and it implements the IDictionary<string, object> interface to let the consumer provide any key-value pairs.

    If you look at this line of the source code or the documentation you can see that there is a constructor overload which accepts the OperationKey.

    So you define your Execute call like this:

    policy.Execute(
        action: context => command.ExecuteNonQuery(),
        contextData: new Context("Command.ExecuteNonQuery()"));
    

    In my opinion it is a bit better approach since you are not relying on string-based key insertion and retrieval.


    UPDATE: I've found the problem.

    You have been using a wrong overload of Execute.

    policy.Execute(
        action: context => command.ExecuteNonQuery(),
        context: new Context("Command.ExecuteNonQuery()"));
    

    Use context instead of contextData and the OperationKey will present.