Skip to content

Instantly share code, notes, and snippets.

@gbzarelli
Last active January 13, 2022 03:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gbzarelli/629288f1b8fa60861df373197149e497 to your computer and use it in GitHub Desktop.
Save gbzarelli/629288f1b8fa60861df373197149e497 to your computer and use it in GitHub Desktop.
Aspect Spring Boot Endpoints
https://stackoverflow.com/questions/33744875/spring-boot-how-to-log-all-requests-and-responses-with-exceptions-in-single-pl
Currently Spring Boot has the Actuator feature to get the logs of requests and responses.
But you can also get the logs using Aspect(AOP).
Aspect provides you with annotations like: @Before, @AfterReturning, @AfterThrowing etc.
@Before logs the request, @AfterReturning logs the response and @AfterThrowing logs the error message, You may not need all endpoints' log, so you can apply some filters on the packages.
Here are some examples:
For Request:
@Before("within(your.package.where.endpoints.are..*)")
public void endpointBefore(JoinPoint p) {
if (log.isTraceEnabled()) {
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs[0] != null) {
log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
}
} catch (JsonProcessingException e) {
}
}
}
Here @Before("within(your.package.where.endpoints.are..*)") has the package path. All endpoints within this package will generate the log.
For Response:
@AfterReturning(value = ("within(your.package.where.endpoints.are..*)"),
returning = "returnValue")
public void endpointAfterReturning(JoinPoint p, Object returnValue) {
if (log.isTraceEnabled()) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
}
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
}
}
Here @AfterReturning("within(your.package.where.endpoints.are..*)") has the package path. All endpoints within this package will generate the log. Also Object returnValue contains the response.
For Exception:
@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
if (log.isTraceEnabled()) {
System.out.println(e.getMessage());
e.printStackTrace();
log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
}
}
Here @AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") has the package path. All endpoints within this package will generate the log. Also Exception e contains the error response.
Here is the full code:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Order(1)
@Component
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
public class EndpointAspect {
static Logger log = Logger.getLogger(EndpointAspect.class);
@Before("within(your.package.where.is.endpoint..*)")
public void endpointBefore(JoinPoint p) {
if (log.isTraceEnabled()) {
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs[0] != null) {
log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
}
} catch (JsonProcessingException e) {
}
}
}
@AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
returning = "returnValue")
public void endpointAfterReturning(JoinPoint p, Object returnValue) {
if (log.isTraceEnabled()) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
}
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
}
}
@AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
if (log.isTraceEnabled()) {
System.out.println(e.getMessage());
e.printStackTrace();
log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
}
}
}
Here, using @ConditionalOnExpression("${endpoint.aspect.enabled:true}") you can enable/disable the log. just add endpoint.aspect.enabled:true into the application.property and control the log
More info about AOP visit here:
Spring docs about AOP
Sample article about AOP
import static java.text.MessageFormat.format;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
@RequiredArgsConstructor
public class EndpointAspectLoggerConfiguration {
private final ObjectMapper mapper;
@AfterThrowing(pointcut = ("within(br.com.helpdev.controller..*)"), throwing = "e")
public void endpointAfterThrowing(final JoinPoint p, final Exception e) {
final var requestData = Arrays.stream(p.getArgs())
.map(this::applyWriteValueAsString)
.collect(Collectors.joining(","));
log.error(format("Signature: {0}; Target: {1}; Error: {2}; Request data: {3}",
p.getSignature().getName(),
p.getTarget().getClass().getSimpleName(),
e.getMessage(),
requestData
), e);
}
private String applyWriteValueAsString(final Object it) {
try {
return mapper.writeValueAsString(it);
} catch (final JsonProcessingException ignored) {
return String.valueOf(it);
}
}
}
@gbzarelli
Copy link
Author

import static java.text.MessageFormat.format;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Arrays;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;

@aspect
@component
@slf4j
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
@requiredargsconstructor
public class EndpointAspectLoggerConfiguration {

private final LabsLogger logger = LabsLogFactory.getLogger(this.getClass());
private final ObjectMapper mapper;

@AfterThrowing(pointcut = ("within(com.xpto.y..controller..)"), throwing = "e")
public void endpointAfterThrowing(final JoinPoint p, final Exception e) {
logger.info(p.getSignature().getName(), p.getTarget().getClass().getSimpleName(),
format("Request objects: {0}", Arrays.stream(p.getArgs())
.map(this::applyWriteValueAsString)
.collect(Collectors.joining(","))
));

logger.error(p.getSignature().getName(), p.getTarget().getClass().getSimpleName(),
    String.valueOf(e.getMessage()), e);

}

private String applyWriteValueAsString(final Object it) {
try {
return mapper.writeValueAsString(it);
} catch (final JsonProcessingException ignored) {
return "null";
}
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment