Writting Custom RabbitMq Exception Handler in Micronaut: A Step-by-Step Guide

RabbitMq in Micronaut

RabbitMq in Micronaut

Asynchronous message processing with RabbitMQ is a common pattern in modern microservices architectures. To ensure the reliability of your message-driven applications, robust exception handling is crucial. In this blog post, we will explore how to integrate RabbitMQ exception handling in a Micronaut application.

Read Also: Mastering Global Exception Handling in Micronaut

This exception handler only logs the exception which gets occurred while serving HTTP request. i.e which have Http Context. To log exception which are happening in background or asynchronously

Step 1: Implement the Exception Handler

We will implement RabbitListenerExceptionHandlerand handle the exception way as per our need. Here I’m logging this exception to Sentry

package com.example.exceptions

import com.fasterxml.jackson.module.kotlin.jsonMapper
import io.micronaut.rabbitmq.exception.RabbitListenerException
import io.micronaut.rabbitmq.exception.RabbitListenerExceptionHandler
import io.sentry.Sentry
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class RabbitMqCustomListenerErrorHandler : RabbitListenerExceptionHandler {
    val log: Logger = LoggerFactory.getLogger("RabbiTMq")
    override fun handle(exception: RabbitListenerException?) {
        log.error(exception.toString())
        val messageState = exception?.messageState
        if (messageState!!.isPresent) {
            val data = String(messageState.get().body, Charsets.UTF_8)
            Sentry.configureScope {
                it.setContexts("message", data)
                it.setContexts("binding_key", messageState.get().envelope.routingKey)
                it.setContexts("properties", jsonMapper().writeValueAsString(messageState.get().properties))
            }
            Sentry.captureException(exception)
        }
    }
}

Step 2: Create a Factory for the Exception Handler

To create a custom error handler for RabbitMQ listeners in Micronaut, you’ll need to define a class annotated with @Factory. This class will replace the default exception handler provided by Micronaut. Below is an example of how to do this:

package com.example.exceptions

import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Replaces
import io.micronaut.rabbitmq.exception.DefaultRabbitListenerExceptionHandler
import jakarta.inject.Singleton

@Factory
class RabbitMqListenerErrorHandler {
    @Replaces(DefaultRabbitListenerExceptionHandler::class)
    @Singleton
    fun handlerConfig(): RabbitMqCustomListenerErrorHandler {
        return RabbitMqCustomListenerErrorHandler()
    }
}

Let’s break down what’s happening here:

  1. We create a Kotlin class called RabbitMqListenerErrorHandler.
  2. We annotate it with @Factory to indicate that this class is responsible for providing a custom error handler.
  3. We define a method named handlerConfig() and annotate it with @Replaces. This annotation tells Micronaut to replace the default DefaultRabbitListenerExceptionHandler with our custom error handler.
  4. Inside handlerConfig(), we instantiate and return an instance of RabbitMqCustomListenerErrorHandler. This is where you can define your custom error handling logic.

Implementation: https://github.com/cw-bhanunadar/Micronaut-playground/pull/10/files

Step 3: Throw a Exception in RabbitMq Consumer

Now it’s time you test the changes, for that raise any kind of exception in a RabbitMq Consumer. The expected behaviour is this exception gets caught by the exception handler and gets logged to Sentry.

@Queue("event-number", prefetch = 1)
    fun consumeEventNumber(eventNumber: Long) {
        logger().error("Successfully received event" + eventNumber.toString())
        throw ArithmeticException()
    }

Step 4: Verify the output

As we have raise Arithmetic Exception, this exception will get logged to Sentry with all the context.

Conclusion

Custom error handling for RabbitMQ listeners in Micronaut provides you with fine-grained control over how exceptions are handled in your message-driven applications. Whether you need to log errors, retry message processing, or take other actions, implementing a custom error handler using the @Factory annotation is a powerful and flexible way to ensure the reliability of your RabbitMQ-based systems.

Related Post