Build Your Own Logging Aspect Spring AOP
Introduction
In modern applications, logging is essential for debugging, monitoring, and auditing. But adding logging statements manually in every method quickly becomes repetive and messy. This is where Aspect-Oriented Programming (AOP) comes in. With Spring AOP, you can separete corss-cutting concerns like logging, security, and transactions from your core business logic.
In this blog, we'll walk through how to create your own Logging Aspect using Spring AOP.
What is Spring AOP?
Spring AOP allows you to define behaviors that should be applied across multiple points in your application without duplicating code.
Key concepts:
- Aspect: A module that encapsulates cross-cutting concerns (e.g., logging).
- Advice: The action taken at a particular join point (before, after, around).
- Pointcut: Expression that defines where advice should be applied.
- Joinpoint: Apoint in program execution (like method call).
- Weaving: Linking aspects with other application code.
Think of AOP as a way to 'plug in' extra behavior around your existing methods without touching their code.
Setting Up Project
Add the following dependency in your pom.xml (for Maven):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Also, enable AOP in your Spring application (in Spring boot, it's automatically enabled):
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAOPTest {
public static void main(String[] args) {
SpringApplication.run(SpringAOPTest.class, args);
}
}
Creating a Logging Aspect
Now let's create an aspect that logs method execution.
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// Pointcut: all methods in service package
@Pointcut("execution(* general.create_aop.service.*.*(..))")
public void serviceMethods() {
}
// Before advice
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
logger.info("Entering method: {}", joinPoint.getSignature().getName());
}
// After advice
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
logger.info("Exiting method: {}", joinPoint.getSignature().getName());
}
// Around advice
@Around("serviceMethods()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
logger.info("Method {} executed in {} ms", joinPoint.getSignature().getName(), elapsed);
return result;
}
@AfterReturning(value = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("Method returned successfully: {}", joinPoint.getSignature().getName());
}
@AfterThrowing(value = "serviceMethods()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
logger.info("Exception in method: {}", joinPoint.getSignature().getName());
logger.info("Error: {}", ex.getMessage());
}
}
The @Around advice wraps the entire method execution. The @After advice runs after the target method completes, but still in scope of the @Around.
So in real project, Having both @Around and @After on the same pointcut may lead to unexpected behavior.
Considering using only @Around if you want to control the entire execution flow.
1. @Before - logBefore() executes first
2. @Around - logExecutionTime() starts (before joinPoint.proceed())
3. Target method executes
4. @Around - logExecutionTime() completes (after joinPoint.proceed())
5. @After - logAfter() executes last
Defining Pointcuts
Pointcuts let you specify where advice applies. For example:
execution(* com.example.service.*.*(..))-> all methods inservicepackage.execution(* com.example.controller.*.*(..))-> all methods incontrollerpackage.
You can combine pointcuts for flexibility.
Testing the Logging AOP
Suppose you have a simmple api:
@Service
public class UserService {
public String getUserById(Long id) throws InterruptedException {
Thread.sleep(500);
return "User-" + id;
}
}
@SpringBootApplication
public class SpringAOPTest {
public static void main(String[] args) {
SpringApplication.run(SpringAOPTest.class, args);
}
@RestController
@RequestMapping("/api/v1")
public static class SpringAOPTestController {
private final UserService userService;
public SpringAOPTestController(UserService userService) {
this.userService = userService;
}
@GetMapping("/test/logging")
public String testLogging() throws InterruptedException {
return userService.getUserById(123L);
}
}
}
When you typing in browser url /api/v1/test/logging, the logs will show:

Notice how logging happens automatically without modifying the service code.
Best Pratices
- Keep aspects focused on once concern (logging, securiry, monitoring, etc.).
- Use proper logging frameworks (SLF4J, Logback).
- Avoid logging sensitive data (like passwords).
- Don't overuse AOP-it's powerful but can make debugging harder if misused.
Bonus example
Build a AOP that measure execution time, annotation-based is clean way to implement this.
Step 1: Create annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
String action();
}
Step 2: Aspect definition
@Aspect
@Component
@Order(100) // run after security, before tx commit
public class AuditAspect {
}
Step 3 — @Around advice
@Aspect
@Component
@Order(100) // run after security, before tx commit
public class AuditAspect {
private static final Logger logger = LoggerFactory.getLogger(AuditAspect.class);
@Around("@annotation(audited)")
public Object audit(
ProceedingJoinPoint pjp,
Audited audited
) throws Throwable {
long start = System.currentTimeMillis();
boolean success = false;
try {
Object result = pjp.proceed();
success = true;
return result;
} catch (Throwable ex) {
throw ex;
} finally {
long duration = System.currentTimeMillis() - start;
logger.info("Audited execution time: {} ms, success: {}, audited action: {}", duration, success, audited.action());
}
}
}
Step 4: Applying the aspect
@SpringBootApplication
public class SpringAOPTest {
public static void main(String[] args) {
SpringApplication.run(SpringAOPTest.class, args);
}
@RestController
@RequestMapping("/api/v1")
public static class SpringAOPTestController {
@GetMapping("/test")
@Audited(action = "execute test()")
public String test() throws InterruptedException {
Thread.sleep(1500);
return "test";
}
}
}
Result
Try to hit this url /api/v1/test, and you can see the logs show:
_
Conclusion
Spring AOP makes it easy to handle cross-cutting concerns like logging without cluttering your business logic. By creating a simple logging aspect, you can monitor method calls, execution times, and flow across your application.
Try extending this to other concerns like security checks, caching, or transaction management - the possibilities are endless!
