Logging Standard Exceptions with Spring AOP
Posted by Tejus Parikh on May 18, 2011
One of the challenges of working on a large system with multiple developers is the management and handling of application errors and exceptions. Often, developers get frustrated with managing typed exceptions and all interfaces either throw or catch Exception. Another common approach is to change all exceptions to RuntimeException to avoid cluttering interfaces. An alternative approached is described in this paper by Andy Longshaw. His approach breaks exceptions into two distinct hierarchies, domain and system. Domain errors caused by logical faults in the application, such as a zero being passed as a divisor to a function. System errors are errors caused through the interaction with other components, like a network timeout when attempting to communicate with the database. Both exception types are checked exceptions, which means they must be caught and handled by each layer of the application. He presents guidelines on when and at which layer each type of exception should be logged. If you are using Java the obvious drawback of using checked exceptions everywhere is that each layer will be filled with code that looks like the following:
try { service.doApiCall(); } catch(DomainException e) { log.error("Domain exception occurred", e); } catch(SystemException e) { log.error("System exception occurred", e); }
The duplication of handling of api exceptions can easily be handled with a custom annotation and Spring AOP. The first step is to create the Boundary logging annotation. This annotation describes where exceptions should be logged.
@Retention(value = RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) public @interface BoundaryLogger { }
Next, we need to create the interceptor. Along with just logging exceptions, I also choose to log the api calls. There are a few steps to creating an Aspect that can be used by Spring-AOP. First added the @Aspect
annotation to the class declaration.
@Service // make this available as a spring bean @Aspect // tell spring this class is an aspect public class BoundaryLoggingInterceptor {
Next, create a method that tells the Aspect to do something before the underlying method, annotated with @BoundaryLogger
, is called. In this example, it’s to log the method invocation.
@Before("@annotation(net.vijedi.springlogging.interceptor.BoundaryLogger)") public void logInvocation(JoinPoint jp) {
Similarly, triggering the aspect at method end can be handled with:
@AfterReturning( pointcut = "@annotation(net.vijedi.springlogging.interceptor.BoundaryLogger)", returning = "retVal") public void logComplete(JoinPoint jp, Object retVal) {
The one we are especially interested in for this purposes of this blog post is exception logging. This is the entire method for logging exceptions:
@AfterThrowing( pointcut = "@annotation(net.vijedi.springlogging.interceptor.BoundaryLogger)", throwing = "ex" ) public void processException(JoinPoint jp, Throwable ex) throws SystemException, DomainException { if(ex instanceof SystemException) { // System exceptions were logged at source // do not log the exception, just the return logReturn(jp, getLog(jp)); throw (SystemException) ex; } else if(ex instanceof DomainException) { logException(jp, ex); throw (DomainException) ex; } else { logException(jp, ex); throw new DomainException(ex); } }
The logException
method is very simple:
private void logException(JoinPoint jp, Throwable ex) { Log log = getLog(jp); log.error(ex.getMessage(), ex); logReturn(jp, log); }
How the logger
is obtained is very important for accurate logging. Otherwise, it will appear that all logging messages are coming from the interceptor and not from the underlying method. Along with the confusion this could cause, it will also make it harder to use automatic log analyzers. However, it is very easy to get a logger in the context of the underlying class.
protected Log getLog(JoinPoint jp) { return LogFactory.getLog(jp.getTarget().getClass()); }
Once the Aspect has been created, the final step is to tell spring what to do when it encounters the annotation @BoundaryLogger
. There are four steps, tell spring to use annotations to configure the environment, to scan the packages the annotations are in, and to create the aop environment.
This is all that’s need to use annotations for logging at the tier boundaries. The complete code for the example is available at my Spring Boundary Logging repo on Github.
- See more at: http://www.tejusparikh.com/2011/logging-standard-exceptions-with-spring-.html#sthash.MNPx8RnM.dpuf