Search code examples
javalog4jaspectjslf4jspring-aop

How do I create aspect point cut for slf4j logger?


I want to add strings to all of my logs to achieve this, I am planing to use aop but I couldn't declare point cut for all of my loggger objects. I am using slf4j logger here is an example log in a class

Logger logger = LoggerFactory.getLogger(InterviewService.class);
logger.error(ex.getMessage(),ex);

enter image description here

I am trying to intercept logger in the ss so before printing that log I can alter arguments in message and add my text I change my code little bit currently I can catch thrown exceptions but still cant intercept logger.error("some error log") messages in aspect method.

@Pointcut("within(org.apache.logging.log4j.*)")
public void logPointcut() {}

@Pointcut("within(*..Log4jLogger.*)")
public void logPointcuts() {}

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

@Pointcut("within(*.error)")
public void logPointcutsMethodw() {}

@Around("logPointcutsMethod() || logPointcuts() || logPointcut() || logPointcutsMethodw()")
    public Object logError(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        String user = "";
        if (state)
            user = getUser();
        logger.error(
            "Exception in {} User :{}.{}() with cause = {}", 
            joinPoint.getSignature().getDeclaringTypeName(),
            user,
            joinPoint.getSignature().getName()
        );
        return result;
    }

To summarise, I want to create pointcut for every logger.error call so I can add my string to every message


Solution

  • Here is a full MCVE for how to solve this in native AspectJ, either using load-time weaving or compile-time weaving with the Slf4J and Log4J libraries on the AspectJ compiler's inpath. Because Spring AOP can only intercept methods within Spring beans/components, but neither Slf4j nor Log4j are Spring components, you need an AOP framework for adults, not an AOP lite solution like Spring AOP. Fortunately, Spring has a very nice AspectJ integration and also explains it in its manual.

    package de.scrum_master.app;
    
    import org.apache.log4j.BasicConfigurator;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Application {
      private static final Logger logger = LoggerFactory.getLogger(Application.class);
    
      public static void main(String[] args) {
        BasicConfigurator.configure();
        logger.info("Hello Slf4j");
        logger.error("uh-oh", new Exception("oops!"));
        logger.error("WTF dude?!?");
      }
    }
    
    package de.scrum_master.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    @Aspect
    public class LogEnhancerAspect {
      @Around("execution(void org.slf4j.Logger.error(String, ..)) && args(msg, ..)")
      public void formatMessageLog(String msg, ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        args[0] = "[MY PREFIX] " + msg;
        joinPoint.proceed(args);
      }
    }
    

    Console log:

    0 [main] INFO de.scrum_master.app.Application  - Hello Slf4j
    7 [main] ERROR de.scrum_master.app.Application  - [MY PREFIX] uh-oh
    java.lang.Exception: oops!
        at de.scrum_master.app.Application.main(Application.java:13)
    8 [main] ERROR de.scrum_master.app.Application  - [MY PREFIX] WTF dude?!?
    

    This is the simplest solution. If you need more information in your log output, such as the caller or special information from the exception, it is very easy to adjust the pointcut. Also, Logger.error(..) has many overloads. My sample pointcut just catches the ones with the first parameter being a String, such as error(String) or error(String, Throwable). If you need others, you need to adjust the aspect. But this is basically how it works.

    Please also note that this will intercept all error logs, even the ones called from outside your application. If you want to limit it to the base package of your application, you have to adjust the pointcut again and use call() rather than execution(), something like within(my.base.pkg..*) && call(void org.slf4j.Logger.error(String, ..)) && args(msg, ..).

    There are so many ways to tweak this solution, it would take hours to list examples.