Why is OperationKey empty in my Polly retry policy?

1.2k views Asked by At

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");
        }
    }
}
2

There are 2 answers

5
Peter Csala On BEST ANSWER

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.

0
Marcus On

Got around this issue by using a custom context instead.

Policy:

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

Execute:

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