Use AOP in a Spring Boot app to log methods calls and their parameters

Viewed 3650

Rather than manually writing code such as

public void someMethod(Object someArg, Object otherArg) {
    logger.trace("someMethod invoked with arguments {}, {}", someArg, otherArg);    

    // method body
}

I would like to generate such statements automatically using AOP. The app in question is a Spring Boot 2.3.3 app.

I don't think it's possible to generate logging statements for all method calls using AOP, only for methods calls on Spring beans. This would be good enough for my current purposes.

Could someone explain the specific steps for how I add an aspect to a Spring Boot app that will generate a log statement such as the above for all method calls on Spring beans? If only public method calls can be intercepted by the aspect, that would be good enough.

2 Answers

It's true that you can only advice public methods of Spring beans if you use the proxy-based Spring AOP framework (personally, I find that this is usually good enough). With native AspectJ weaving you could advice any method.

First, create an aspect class annotated with @Aspect, and either with @component for component scanning or declared as a @Bean in your configuration.

Then, in the class, you need a pointcut that defines which methods are adviced, and an advice to tell what to do (e.g., do some logging in your case).

A pointcut to advice all public methods or Spring Beans would look like this:

@Pointcut("execution(public * *(..))")
public void publicMethod() {}

Or if you just want to log service methods, you could use:

@Pointcut("within(@org.springframework.stereotype.Service *)")
public void withinService() {}

An advice can run before (@Before), after (@After) or both (@Around) your methods. For logging, I would use an @Around advice. Note that you can also combine pointcuts with logical operators:

@Around("publicMethod() && withinService()")
public Object profileServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable {
    // do some logging before method execution
    Object retVal = joinPoint.proceed();
    // and some logging after method execution
    return retVal;
}

You can also look at my ready-to-use AOProfiling Spring Boot starter for a full example.

@Aspect
@Configuration
public class LoggingAspectConfig {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("within(@org.springframework.stereotype.Repository *)"
            + " || within(@org.springframework.stereotype.Service *)"
            + " || within(@org.springframework.web.bind.annotation.RestController *)")
    public void springBeanPointcut() {
        throw new UnsupportedOperationException();
    }

    @Pointcut("within(com.softilys.soyouz.repository..*)"
            + " || within(com.softilys.soyouz.service.helpers.impl..*)"
            + " || within(com.softilys.soyouz.rest.web..*)")
    public void applicationPackagePointcut() {
        throw new UnsupportedOperationException();
    }

    @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        logger.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null ? e.getMessage() : "NULL");
    }

    @Around("applicationPackagePointcut() && springBeanPointcut()")
    public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
        final String joinPoints = Arrays.toString(joinPoint.getArgs());
        if (joinPoints != null) {
            logger.info("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(), joinPoints);
        }
        final Object result = joinPoint.proceed();
        logger.info("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), result);
        return result;
    }

}
Related