Search code examples
springspring-bootlog4jaopaspect

Why is AOP Logging not working in my project


I'm wasting a lot of time right now with AOP logging setup. I don't know why AOP isn't working in my project. I think I've done all the settings I can. Please let me know if you guys have a solutions. Thank you.

  • application.java
@EnableAspectJAutoProxy
@SpringBootApplication
@ComponentScan(basePackages = "com.demo.apiservice")
@MapperScan("com.demo.apiservice.mapper")
public class ApiServiceApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
      return builder.sources(ApiServiceApplication.class);
    }

    public static void main(String[] args) {
      SpringApplication.run(ApiServiceApplication.class, args);
    }

    @Bean
    public ModelMapper modelMapper() {
       return new CustmizedModelMapper();
    }

    @Bean
    public AopLoggingConfig loggingAspect(){
       return new AopLoggingConfig();
    }
}
  • build.gradle

      configurations {
              all {
                 exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
              }
      }
    
      dependencies {
              //implementation 'org.springframework.boot:spring-boot-starter-security:2.5.5'
              implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
              implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.5.5'
              implementation 'org.springframework.boot:spring-boot-starter-mail:2.5.5'
              implementation 'org.springframework.boot:spring-boot-starter-aop:2.5.5'
          implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
          testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.3'
              implementation 'org.springframework.boot:spring-boot-starter-data-rest:2.5.5'
          implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
              implementation 'org.projectlombok:lombok:1.18.8'
              implementation 'org.modelmapper:modelmapper:2.3.8'
              implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.5'
              implementation 'org.springframework.boot:spring-boot-starter-jdbc:2.5.5'
              implementation 'com.h2database:h2:1.4.200'
              implementation 'org.springframework.boot:spring-boot-configuration-processor:2.5.5'
              implementation 'org.springframework.security:spring-security-core:5.4.2'
              implementation 'com.fasterxml.jackson.core:jackson-core:2.12.3'
              implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.3'
              implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
              implementation 'org.apache.httpcomponents:httpclient:4.5.13'
              implementation 'com.nimbusds:nimbus-jose-jwt:9.7'
              implementation 'io.springfox:springfox-swagger2:3.0.0'
              implementation 'io.springfox:springfox-swagger-ui:2.9.2'
              implementation 'joda-time:joda-time:2.10.10'
              implementation 'io.jsonwebtoken:jjwt-api:0.11.1'
              implementation 'javax.inject:javax.inject:1'
              implementation 'com.googlecode.json-simple:json-simple:1.1'
              implementation 'de.mkammerer:argon2-jvm:2.7'
              implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
              implementation 'org.apache.maven.plugins:maven-surefire-plugin:2.22.0'
              implementation 'javax.validation:validation-api:2.0.1.Final'
              implementation 'org.postgresql:postgresql:42.1.4'
              implementation 'org.hibernate:hibernate-gradle-plugin:5.6.1.Final'
              implementation 'com.jayway.jsonpath:json-path:2.6.0'
              compileOnly 'org.projectlombok:lombok:1.18.8'
          testCompileOnly 'org.projectlombok:lombok:1.18.8'
              runtimeOnly 'org.springframework.boot:spring-boot-devtools:2.5.5'
          annotationProcessor 'org.projectlombok:lombok:1.18.8'
          testAnnotationProcessor 'org.projectlombok:lombok:1.18.8'
              testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.5'
              testImplementation 'org.springframework.security:spring-security-test:5.5.2'
      }
    
  • AopLoggingComponent.java

      package com.demo.apiservice.config;
    
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.stereotype.Component;
    
      @Component
      @Aspect
      public class AopLoggingConfig {
    
          Logger logger =  LoggerFactory.getLogger(AopLoggingConfig.class);
    
          static String name = "";
    
          static String type = "";
    
          /**
              *   AspectJ is applied only to a specific class/method in package.
              */
          @Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
          public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
              type = joinPoint.getSignature().getDeclaringTypeName();
    
              if (type.indexOf("Controller")  -1) {
                  name = "Controller  \t:  ";
              }
              else if (type.indexOf("Service")  -1) {
                  name = "ServiceImpl  \t:  ";
              }
              else if (type.indexOf("DAO")  -1) {
                  name = "DAO  \t\t:  ";
              }
              logger.debug(name + type + ".@@@@@@@@@@@@@@@@@@@@@@@@@ " + joinPoint.getSignature().getName() + "()");
              return joinPoint.proceed();
          }
      }
    
  • controller.java

      package com.demo.apiservice.customer;
    
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.PutMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestHeader;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.ResponseBody;
      import org.springframework.web.bind.annotation.RestController;
    
      import com.demo.apiservice.constant.Constants;
      import com.demo.apiservice.customer.service.CustomerService;
      import com.demo.apiservice.customer.service.impl.CustomerServiceImpl;
      import com.demo.apiservice.request.CustomerRequest;
      import com.demo.apiservice.request.LoginRequest;
      import com.demo.apiservice.response.GetCustomerResponse;
      import com.demo.apiservice.response.SuccessResponse;
      import com.demo.apiservice.utils.ResponseHandler;
    
      import io.swagger.annotations.Api;
      import io.swagger.annotations.ApiOperation;
      import io.swagger.annotations.ApiParam;
      import io.swagger.annotations.ApiResponse;
      import io.swagger.annotations.ApiResponses;
      import lombok.extern.slf4j.Slf4j;
    
      import java.util.List;
    
      import javax.validation.Valid;
    
      @Slf4j
      @RestController
      @Api(tags = "Customers APIs")
      @RequestMapping("/apiservice/v1/customers")
      public class CustomerController {
    
              @Autowired
              private CustomerService customerService;
    
              @GetMapping
              @ApiOperation(value = "List of Customers API")
              @ApiResponses(value = {
                        @ApiResponse(code = 400, message = Constants.BAD_REQUEST),
                        @ApiResponse(code = 403, message = Constants.ACCESS_DENIED)})
              public ResponseEntity<Object retrieveAll() {
                 log.info("Start of CustomerController::retrieveAll method");
                 return customerService.retrieveAll();
              }
      }
    
  • application.yml

      logging:
        level:
              org:
               springframework.web: DEBUG
               hibernat: DEBUG
              com:
               atoz_develop:
                 mybatissample:
                      repository: TRACE
    
      mybatis:
        mapper-locations: classpath:/mappers/*.xml
        type-aliases-package: com.demo.apiservice.entity
        configuration:
              map-underscore-to-camel-case: 'true'
    
      debug: 'true'
    
      spring:
        datasource:
              driver-class-name: org.postgresql.Driver
              username: postgres
              url: jdbc:postgresql://localhost:5432/postgres
              platform: postgres
              password: postgres
        jpa:
              generate-ddl: 'false'
              properties:
               hibernate:
                 dialect: org.hibernate.dialect.PostgreSQL10Dialect
                 format_sql: 'true'
              hibernate:
               ddl-auto: update
              show-sql: 'true'
    
  • stack trace log

      2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet                      : GET /apiservice/v1/customers, parameters={}        2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.demo.apiservice.customer.CustomerController#retrieveAll()
      2021-11-17 16:05:19.993  INFO 23300 --- [nio-8080-exec-8] c.l.a.c.CustomerController                        : Start of CustomerController::retrieveAll method
      2021-11-17 16:05:19.993  INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl               : Inside of the CustomerServiceImpl :: retrieveAll method
      2021-11-17 16:05:19.996  INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl               : End of the CustomerServiceImpl :: retrieveAll method
      2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using application/xhtml+xml, given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
      2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{data=[Customer([email protected], password=null, customerId=null, storeName=eos, firstname=test1 (truncated)...]
      2021-11-17 16:05:19.998 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet                      : Completed 200 OK
    

Solution

  • Your aspect is triggered. I added an explicit controller method call in order to check:

      public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ApiServiceApplication.class, args);
        context.getBean(CustomerController.class).retrieveAll();
      }
    

    Then I fixed some typos in your code like the one mentioned in my previous comment.

    Probably your problem was that you simply did not see the log output because you forgot the log configuration for your application package com.demo.apiservice:

    logging:
      level:
        org:
          springframework.web: DEBUG
          hibernate: DEBUG
        com:
          atoz_develop:
            mybatissample:
              repository: TRACE
          demo.apiservice: DEBUG
    

    BTW, I also corrected your typo hibernat to hibernate, but that is unrelated to the problem at hand.

    Then I see this in the log:

    [  restartedMain] o.s.b.w.e.t.TomcatWebServer              : Tomcat started on port(s): 8080 (http) with context path ''
    [  restartedMain] c.d.a.ApiServiceApplication              : Started ApiServiceApplication in 5.101 seconds (JVM running for 6.117)
    [  restartedMain] c.d.a.c.AopLoggingConfig                 : Controller     :  com.demo.apiservice.customer.CustomerController.@@@@@@@@@@@@@@@@@@@@@@@@@ retrieveAll()
    [  restartedMain] c.d.a.c.AopLoggingConfig                 : Controller     :  com.demo.apiservice.customer.CustomerController.@@@@@@@@@@@@@@@@@@@@@@@@@ retrieveAll()
    [  restartedMain] c.d.a.c.CustomerController               : Start of CustomerController::retrieveAll method
    

    Do you see the problem? You get duplicate logging, because the aspect is picked up once by component scan and instantiated one more time as a bean in your application configuration. So you need to remove this part from ApiServiceApplication:

      @Bean
      public AopLoggingConfig loggingAspect() {
        return new AopLoggingConfig();
      }
    

    Now the duplicate logging is gone.

    Next, maybe you want to simplify your aspect a bit and simply log joinPoint or joinPoint.getSignature(). You also want to make name and type local variables, because the static fields are not thread-safe. Instead, probably you want a static logger in the aspect.

    @Component
    @Aspect
    public class AopLoggingConfig {
      private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
    
      @Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
      public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
        String type = joinPoint.getSignature().getDeclaringTypeName();
        String name = "";
    
        if (type.contains("Controller")) {
          name = "Controller  \t:  ";
        }
        else if (type.contains("Service")) {
          name = "ServiceImpl  \t:  ";
        }
        else if (type.contains("DAO")) {
          name = "DAO  \t\t:  ";
        }
    
        logger.debug(name + joinPoint.getSignature());
        return joinPoint.proceed();
      }
    }
    

    The log line becomes:

    Controller      :  ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
    

    But actually, both the package name and the class name indicate that we are dealing with a controller, DAO or service. So why bother with the if-else stuff in the first place? Why make a simple matter complicated and the aspect slower? Besides, if you only want to log something and not influence the control flow, the method parameters or the return value, a simple @Before advice would do, the expensive @Around is unnecessary.

    @Component
    @Aspect
    public class AopLoggingConfig {
      private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
    
      @Before("execution(* com.demo.apiservice.customer.*Controller.*(..))")
      public void logPrint(JoinPoint joinPoint) {
        logger.debug(joinPoint.getSignature().toString());
      }
    }
    

    The log line becomes:

    ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
    

    Isn't that enough?

    Or even simpler:

        logger.debug(joinPoint.toString());
    

    Log:

    execution(ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll())
    

    Keep it simple!