BEagle1984 / silverback

Silverback is a simple but feature-rich message bus for .NET core (it currently supports Kafka, RabbitMQ and MQTT).
https://silverback-messaging.net
MIT License
257 stars 37 forks source link

Add exception to message before posting to Dead Letter #199

Closed viiny123 closed 6 months ago

viiny123 commented 1 year ago

Hey guys,

I have a scenario where I would like to add the exception to the message before publishing it in the dead letter, I created a base message where it has the error property, and all messages inherit from it, but I did not find a way to add the exception to the message , I tried placing a try/catch in an IConsumerBehavior that I created, but the exception does not arrive, I tried the IKafkaConsumerErrorCallback, but no event arrived even after I forced an exception, finally I thought of developing my own error policy, but the IErrorPolicyChainBuilder interface did not let me add my customErrorPolicy, I would like to do something along those lines here:

public class ConsumerExceptionErrorPolicy : ErrorPolicyBase
{
    protected override ErrorPolicyImplementation BuildCore(IServiceProvider serviceProvider) =>
        new ConsumerExceptionErrorPolicyImplementation(
            MaxFailedAttemptsCount,
            ExcludedExceptions,
            IncludedExceptions,
            ApplyRule,
            MessageToPublishFactory,
            serviceProvider,
            serviceProvider.GetRequiredService<IInboundLogger<ConsumerExceptionErrorPolicy>>());

    private sealed class ConsumerExceptionErrorPolicyImplementation : ErrorPolicyImplementation
    {
        public ConsumerExceptionErrorPolicyImplementation(
            int? maxFailedAttempts,
            ICollection<Type> excludedExceptions,
            ICollection<Type> includedExceptions,
            Func<IRawInboundEnvelope, Exception, bool>? applyRule,
            Func<IRawInboundEnvelope, Exception, object?>? messageToPublishFactory,
            IServiceProvider serviceProvider, IInboundLogger<ErrorPolicyBase> logger)
            : base(maxFailedAttempts, excludedExceptions, includedExceptions, applyRule, messageToPublishFactory, serviceProvider, logger)
        {
        }

        protected override Task<bool> ApplyPolicyAsync(ConsumerPipelineContext context, Exception exception)
        {
            if (context.Envelope is IInboundEnvelope<IBrokerMessage> { Message: not null } inboundEnvelop)
            {
                inboundEnvelop.Message.ErrorMessage = exception.Message;
            }

            return Task.FromResult(true);
        }
    }
}
BEagle1984 commented 1 year ago

So you are basically trying to catch the exception from the subscriber and set it to a message property before republishing it to the dead letter topic. Right?

Are you willing to publish the message to the dead letter topic via the MoveMessageErrorPolicy or your own mechanism?

In any case, an IConsumerBehavior is where you want to start most probably. The implementation should be fairly straightforward.

public class CustomExceptionHandlerConsumerBehavior : IConsumerBehavior
{
    public int SortIndex => 1900;

    public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            if (context.Envelope.Message is IBrokerMessage brokerMessage)
            {
                brokerMessage.ErrorMessage = exception.Message;
            }
        }
    }
}

You register it with .AddSingletonBrokerBehavior<CustomExceptionHandlerConsumerBehavior >() and it should enrich your message.

Please note that the MoveMessageErrorPolicy adds the exception type and source to a header already (which I personally find more elegant than modifying the message body). I don't add the whole message by default to prevent unwanted information disclosure. (You can easily adapt the above code snippet to write the exception message to a header instead of the message body and it should also be propagated by the MoveMessageErrorPolicy .)

marcospiresvortigo commented 1 year ago

Hi Sergio,

The "Silverback.Messaging.Inbound.Transaction.TransactionHandlerConsumerBehavior", does not propagate the exception to the other Behavior, it simply does not fall into the catch

catch (Exception ex)
        {
            if (context.Envelope.Message is IBrokerMessage brokerMessage)
            {
                brokerMessage.ErrorMessage = exception.Message;
            }
        }

in my Behavior, I already tried to do it the same way you gave an example.

BEagle1984 commented 1 year ago

Why don't you simply plug your behavior after the TransactionHandlerConsumerBehavior (which propagates the exception only if there is no error policy to be applied)?