hitchhiker
hitchhiker

Reputation: 1319

Patterns for exception handling in Spring Boot using beans or AOP

I have a Spring Boot server application where exceptions are caught, logged and handled when possible and when not possible they are handled by @ControllerAdvice as the last resort.

I want to add more logic for exception handling, for example, sending a metric to a monitoring solution on each exception. For example there's a Spring bean MetricsBean which is a service which sends metrics. Using MetricsBean is easy for errors which are handled by @ControllerAdvice because it's also a Spring bean.

However using MetricsBean is not as straightforward in such code:

try {
  // business logic
} catch (Exception ex) {
  logger.error(ex)
}

because that would mean manually autowiring MetricsBean into each service class. Also logger object is not a Spring bean.

So I have a dilemma how do I send a metric from inside or around logger.error() method when the logger class itself is not a Spring bean. I'm wondering what the best practice is in such cases. Here're some of the options:

  1. Should logger also be a Spring bean? (seems tedious and error-prone)
  2. Should there be a custom Exception extending class which would be a Spring bean and will send a metric inside its constructor? (seems tedious and error-prone)
  3. Should Spring AOP be used with the pointcut being logger.error() method?

I'm surprised that there's not an established solution for this pattern for such an established framework as Spring (most of the solutions focus on handling errors in @ControllerAdvice).

I'd like the solution to be as generic as possible (point 3 seems to win there) but perhaps there's a pattern which I'm missing.

Upvotes: 0

Views: 1102

Answers (1)

Hans-Christian
Hans-Christian

Reputation: 687

Not sure if it is the best practice but I would use a UtilityClass as a facade exposing a static method. In that method, wire the metrics registry using Metrics.globalRegistry to avoid injection. This will avoid coupling the metric details from where it is used.

You can then either invoke the static method everywhere you are handling an exception (so besides logger.error(ex)) or combine this with your third option with the pointcut.

Simple example:

@UtilityClass
public class ExceptionUtility {
  public static void handleException(Throwable throwable) {
    Counter.builder("some.name.exception")
        .tag("name", throwable.getClass().getName())
        .register(Metrics.globalRegistry)
        .increment();
  }
}

Usage:

    try {
    ...  
    } catch (RestClientException restClientException) {
      ExceptionUtility.handleException(restClientException);
    }

Upvotes: 1

Related Questions