2. AOP for logs in Spring Boot

Bryam David Vega Moreno
5 min readMay 11, 2023

--

The purpose of this article is to demonstrate the potential of aspect-oriented programming to carry out an audit for various web services using logs.

When developing web services, it is always important to know when the service was started and whether the service was completed successfully. One way to audit these web services is to use logs on the services to know which service was started and where it ended.

This audit is very important because having a large amount of logs stored in a file, being able to know through a generic message where a log was started is very helpful to review what the ws did in a faster way and without wasting a lot of time.

However, when it comes to coming up with an audit for these ws, many developers tend to put a log at the start and end of the ws to know when the process started and ended. This example can be seen like this:

@GetMapping()
public ResponseEntity<String> findAll(){
try{
log.info("Starting execution method findAll");
String result = "Hello world";
log.info("Finished execution method findAll");
return new ResponseEntity<>(result, HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}

As seen in the example, this may be a solution, however, we may find that the code does not look as clean and generates a bit of noise when trying to read the code. A solution proposed in this article to solve this issue is to use aspect-oriented programming to handle this audit better and keep the code cleaner.

What Is Aspect-Oriented Programming (AOP)?

Aspect-oriented programming (AOP) is a programming paradigm that focuses on modularity and separation of concerns in a software system. Unlike object-oriented programming (OOP), which is based on data encapsulation and interaction between objects, aspect-oriented programming focuses on cross-cutting aspects that affect multiple objects and system components.

In OOP, aspects represent specific concerns or behaviours that cut across multiple modules of a system. These aspects can be common aspects such as security, event logging, transaction management, exception handling, among others. The main idea is to isolate these aspects and modularise them so that they can be reused and maintained independently.

How to apply AOP to manage logs ?

The first thing we do is to create a Spring Boot project using Spring Initializr or your preferred manager. In addition to that, we add the Spring Web dependency to enable the use of web services. Also we add Lombok Dependency to use Log4j library to manage logs.

In build.gradle add aop dependency

implementation 'org.springframework.boot:spring-boot-starter-aop'

Once the project is created, the first thing we do is to create an annotation called LogExecutionTime. This annotation will be used to mark the ws methods to print the logs for better auditing.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

For this case we are creating a java annotation called LogExecutionTime which contains some interesting annotations:

  • @Target(ElementType.METHOD): This annotation indicates that the LogExecutionTime annotation can be applied only to elements of type METHOD, i.e. methods.
  • @Retention(RetentionPolicy.RUNTIME): This annotation specifies that the LogExecutionTime annotation shall be available at runtime, meaning that it can be accessed through reflection at runtime.
  • @interface : Is used in Java to define a new custom annotation. Basically, it is a way to create your own annotations with specific metadata that can be applied to code elements such as classes, methods or variables.

After creating this annotation, we proceed to create a class called LoginAspect that will be in charge of auditing the web service through a method called logExecutionTime. Next we present the development of the class

@Aspect
@Component
@Slf4j
public class LoggingAspect {

@Around("(@annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PatchMapping) || @annotation(org.springframework.web.bind.annotation.DeleteMapping)) " +
"&& @annotation(com.devsu.challenge.config.aop.LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();

log.info("Starting execution method {}", methodName);

Object result = joinPoint.proceed();

log.info("Finished execution method {}", methodName);

return result;
}
}

In this class we also have some interesting annotations that you need to understand what they are used for.

  • @Aspect : This annotation marks the LoggingAspect class as an aspect in AOP. An aspect is a module that encapsulates a cross-cutting behaviour that can be applied to various breakpoints in a system.
  • @Component: This annotation marks the LoggingAspect class as a Spring component, allowing it to be detected and managed by the Spring container.
  • @Slf4j: This annotation is part of the Lombok project and is used to automatically generate a logger (in this case, using the SLF4J library) in the class. The logger is used to print log messages.

The logExecutionTime method is the breakpoint of the aspect and is executed when a certain condition defined in the @Around annotation is met. Here is its step-by-step explanation:

  • @Around : The logExecutionTime method is the breakpoint of the aspect and is executed when a certain condition defined in the @Around annotation is met.
  • Inside the Around annotation we are indicating that the logic of the logExecutionTime method for Get, Post, Delete, Patch requests will be executed and that they must also be annotated with the annotation we create.

Inside the logExecutionTime method, the following is done:

  • The name of the method being executed is obtained using the joinPoint object.
  • An information message is recorded in the logger, indicating that the execution of the method has started.
  • He proceed() method of the joinPoint object is called, which allows the execution of the method to continue normally.
  • After the method has finished executing, another information message is logged in the logger, indicating that the execution of the method has been completed.
  • Finally, the method returns the result returned by the original method.

Finally, we just need to create an example controller class that has a get request.

@RestController
public class ExampleController {

@GetMapping()
@LogExecutionTime
public ResponseEntity<String> getHello(){
try{
String result = "Hello world";
return new ResponseEntity<>(result, HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}

Once the controller has been made, we realise that we also put the annotation we created to the method. Finally we just run the project and call the endpoint and we get the following result:

With this we can see that it is no longer necessary to create a log to start and end the method, but through aspect-oriented programming we eliminate that need by using custom annotations and the use of aspects.

I hope this article will be very useful!

--

--

Bryam David Vega Moreno

Senior Software Developer | Data Scientist | software architect