Skip to content

Commit e610219

Browse files
committed
feat: implement auditing and logging enhancements with new exception handling and context management
1 parent 63a84ef commit e610219

32 files changed

+622
-226
lines changed

backend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM eclipse-temurin:11
1+
FROM --platform=linux/amd64 eclipse-temurin:11
22

33
ADD target/utmstack.war ./
44

backend/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
4949
<archunit-junit5.version>0.21.0</archunit-junit5.version>
5050
<mapstruct.version>1.4.2.Final</mapstruct.version>
51+
<lombok.version>1.18.34</lombok.version>
5152
<!-- Plugin versions -->
5253
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
5354
<maven-site-plugin.version>3.9.1</maven-site-plugin.version>
@@ -160,6 +161,12 @@
160161
<version>${mapstruct.version}</version>
161162
<scope>provided</scope>
162163
</dependency>
164+
<dependency>
165+
<groupId>org.projectlombok</groupId>
166+
<artifactId>lombok</artifactId>
167+
<version>${lombok.version}</version>
168+
<scope>provided</scope>
169+
</dependency>
163170
<dependency>
164171
<groupId>org.springframework.boot</groupId>
165172
<artifactId>spring-boot-configuration-processor</artifactId>
@@ -466,6 +473,11 @@
466473
<artifactId>spring-boot-configuration-processor</artifactId>
467474
<version>${spring-boot.version}</version>
468475
</path>
476+
<path>
477+
<groupId>org.projectlombok</groupId>
478+
<artifactId>lombok</artifactId>
479+
<version>${lombok.version}</version>
480+
</path>
469481
<path>
470482
<groupId>org.mapstruct</groupId>
471483
<artifactId>mapstruct-processor</artifactId>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.park.utmstack.advice;
2+
3+
4+
import com.park.utmstack.security.TooMuchLoginAttemptsException;
5+
import com.park.utmstack.service.application_events.ApplicationEventService;
6+
import com.park.utmstack.util.UtilResponse;
7+
import com.park.utmstack.util.exceptions.IncidentAlertConflictException;
8+
import com.park.utmstack.util.exceptions.NoAlertsProvidedException;
9+
import com.park.utmstack.util.exceptions.TfaVerificationException;
10+
import com.park.utmstack.util.exceptions.TooManyRequestsException;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.http.HttpStatus;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.security.authentication.BadCredentialsException;
16+
import org.springframework.web.bind.annotation.ExceptionHandler;
17+
import org.springframework.web.bind.annotation.RestControllerAdvice;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
import java.util.NoSuchElementException;
21+
22+
@Slf4j
23+
@RestControllerAdvice
24+
@RequiredArgsConstructor
25+
public class GlobalExceptionHandler {
26+
27+
private final ApplicationEventService applicationEventService;
28+
29+
@ExceptionHandler(TfaVerificationException.class)
30+
public ResponseEntity<?> TfaVerificationException(TfaVerificationException e, HttpServletRequest request) {
31+
return UtilResponse.buildErrorResponse(HttpStatus.PRECONDITION_FAILED, e.getMessage());
32+
}
33+
34+
@ExceptionHandler(BadCredentialsException.class)
35+
public ResponseEntity<?> handleForbidden(BadCredentialsException e, HttpServletRequest request) {
36+
return UtilResponse.buildUnauthorizedResponse(e.getMessage());
37+
}
38+
39+
@ExceptionHandler(TooMuchLoginAttemptsException.class)
40+
public ResponseEntity<?> handleTooManyLoginAttempts(TooMuchLoginAttemptsException e, HttpServletRequest request) {
41+
return UtilResponse.buildLockedResponse(e.getMessage());
42+
}
43+
44+
@ExceptionHandler(NoSuchElementException.class)
45+
public ResponseEntity<?> handleNotFound(NoSuchElementException e, HttpServletRequest request) {
46+
return UtilResponse.buildNotFoundResponse(e.getMessage());
47+
}
48+
49+
@ExceptionHandler(TooManyRequestsException.class)
50+
public ResponseEntity<?> handleTooManyRequests(TooManyRequestsException e, HttpServletRequest request) {
51+
return UtilResponse.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS, e.getMessage());
52+
}
53+
54+
@ExceptionHandler({NoAlertsProvidedException.class})
55+
public ResponseEntity<?> handleNoAlertsProvided(Exception e, HttpServletRequest request) {
56+
return UtilResponse.buildErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage());
57+
}
58+
59+
@ExceptionHandler(IncidentAlertConflictException.class)
60+
public ResponseEntity<?> handleConflict(IncidentAlertConflictException e, HttpServletRequest request) {
61+
return UtilResponse.buildErrorResponse(HttpStatus.CONFLICT, e.getMessage());
62+
}
63+
64+
@ExceptionHandler(Exception.class)
65+
public ResponseEntity<?> handleGenericException(Exception e, HttpServletRequest request) {
66+
return UtilResponse.buildInternalServerErrorResponse(e.getMessage());
67+
}
68+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.park.utmstack.aop.logging;
2+
3+
import com.park.utmstack.domain.application_events.enums.ApplicationEventType;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target(ElementType.METHOD)
11+
@Retention(RetentionPolicy.RUNTIME)
12+
public @interface AuditEvent {
13+
ApplicationEventType attemptType();
14+
String attemptMessage();
15+
16+
ApplicationEventType successType();
17+
String successMessage();
18+
}
19+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.park.utmstack.aop.logging;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.METHOD)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface Loggable {
11+
}
12+
13+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.park.utmstack.aop.logging;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface NoLogException {}
11+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.park.utmstack.aop.logging.impl;
2+
3+
import com.park.utmstack.aop.logging.AuditEvent;
4+
import com.park.utmstack.aop.logging.NoLogException;
5+
import com.park.utmstack.domain.application_events.enums.ApplicationEventType;
6+
import com.park.utmstack.domain.shared_types.ApplicationLayer;
7+
import com.park.utmstack.loggin.LogContextBuilder;
8+
import com.park.utmstack.service.application_events.ApplicationEventService;
9+
import com.park.utmstack.service.dto.auditable.AuditableDTO;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import net.logstash.logback.argument.StructuredArguments;
13+
import org.aspectj.lang.ProceedingJoinPoint;
14+
import org.aspectj.lang.annotation.Around;
15+
import org.aspectj.lang.annotation.Aspect;
16+
import org.aspectj.lang.reflect.MethodSignature;
17+
import org.slf4j.MDC;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.UUID;
23+
24+
import static com.park.utmstack.config.Constants.*;
25+
26+
@Aspect
27+
@Component
28+
@Slf4j
29+
@RequiredArgsConstructor
30+
public class AuditAspect {
31+
32+
private final ApplicationEventService applicationEventService;
33+
private final LogContextBuilder logContextBuilder;
34+
35+
@Around("@annotation(auditEvent)")
36+
public Object logAuditEvent(ProceedingJoinPoint joinPoint, AuditEvent auditEvent) throws Throwable {
37+
return handleAudit(joinPoint, auditEvent.attemptType(), auditEvent.successType(),
38+
auditEvent.attemptMessage(), auditEvent.successMessage());
39+
}
40+
41+
private Object handleAudit(ProceedingJoinPoint joinPoint,
42+
ApplicationEventType attemptType,
43+
ApplicationEventType successType,
44+
String attemptMessage,
45+
String successMessage) throws Throwable {
46+
47+
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
48+
String context = signature.getDeclaringType().getSimpleName() + "." + signature.getMethod().getName();
49+
String traceId = UUID.randomUUID().toString();
50+
51+
MDC.put(TRACE_ID_KEY, traceId);
52+
MDC.put(CONTEXT_KEY, context);
53+
54+
Map<String, Object> extra = extractAuditData(joinPoint.getArgs());
55+
56+
extra.put(LAYER_KEY, ApplicationLayer.CONTROLLER.getValue());
57+
58+
try {
59+
applicationEventService.createEvent(attemptMessage, attemptType, extra);
60+
61+
Object result = joinPoint.proceed();
62+
63+
if (successType != ApplicationEventType.UNDEFINED) {
64+
applicationEventService.createEvent(successMessage, successType, extra);
65+
}
66+
67+
return result;
68+
69+
} catch (Exception e) {
70+
if (!e.getClass().isAnnotationPresent(NoLogException.class)) {
71+
String msg = String.format("%s: %s", context, e.getMessage());
72+
log.error(msg, e, StructuredArguments.keyValue("args", logContextBuilder.buildArgs(e)));
73+
}
74+
75+
throw e;
76+
}
77+
}
78+
79+
private Map<String, Object> extractAuditData(Object[] args) {
80+
Map<String, Object> extra = new HashMap<>();
81+
for (Object arg : args) {
82+
if (arg instanceof AuditableDTO) {
83+
AuditableDTO auditable = (AuditableDTO) arg;
84+
extra.putAll(auditable.toAuditMap());
85+
}
86+
}
87+
return extra;
88+
}
89+
}
90+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.park.utmstack.aop.logging.impl;
2+
3+
import com.park.utmstack.config.Constants;
4+
import com.park.utmstack.loggin.LogContextBuilder;
5+
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
7+
import net.logstash.logback.argument.StructuredArguments;
8+
import org.aspectj.lang.ProceedingJoinPoint;
9+
import org.aspectj.lang.annotation.Around;
10+
import org.aspectj.lang.annotation.Aspect;
11+
import org.slf4j.MDC;
12+
import org.springframework.stereotype.Component;
13+
14+
@Aspect
15+
@Component
16+
@RequiredArgsConstructor
17+
@Slf4j
18+
public class LoggingMethodAspect {
19+
private final LogContextBuilder logContextBuilder;
20+
21+
@Around("@annotation(com.park.utmstack.aop.logging.Loggable)")
22+
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
23+
String traceId = MDC.get(Constants.TRACE_ID_KEY);
24+
String methodName = joinPoint.getSignature().toShortString();
25+
long start = System.currentTimeMillis();
26+
27+
try {
28+
Object result = joinPoint.proceed();
29+
long duration = System.currentTimeMillis() - start;
30+
String msg = String.format("Method %s executed successfully in %sms", methodName, duration);
31+
log.info( msg, StructuredArguments.keyValue("args", logContextBuilder.buildArgs(methodName, String.valueOf(duration))));
32+
return result;
33+
} catch (Exception ex) {
34+
String msg = String.format("%s Method %s failed: 5s", traceId, methodName);
35+
log.error(msg, ex, StructuredArguments.keyValue("args", logContextBuilder.buildArgs(ex)));
36+
throw ex;
37+
}
38+
}
39+
}

backend/src/main/java/com/park/utmstack/config/Constants.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ public final class Constants {
124124
public static final String FRONT_BASE_URL = "https://10.21.199.3";
125125
public static final String PDF_SERVICE_URL = "http://web-pdf:8080/generate-pdf";
126126

127+
// ----------------------------------------------------------------------------------
128+
// Defines the index pattern for querying Elasticsearch statistics indexes.
129+
// ----------------------------------------------------------------------------------
130+
public static final String STATISTICS_INDEX_PATTERN = "v11-statistics-*";
131+
132+
// Logging
133+
public static final String TRACE_ID_KEY = "traceId";
134+
public static final String CONTEXT_KEY = "context";
135+
public static final String USERNAME_KEY = "username";
136+
public static final String METHOD_KEY = "method";
137+
public static final String PATH_KEY = "path";
138+
public static final String REMOTE_ADDR_KEY = "remoteAddr";
139+
public static final String DURATION_KEY = "duration";
140+
public static final String CAUSE_KEY = "cause";
141+
public static final String LAYER_KEY = "layer";
142+
143+
127144
private Constants() {
128145
}
129146
}

backend/src/main/java/com/park/utmstack/config/LoggingConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import ch.qos.logback.classic.LoggerContext;
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.park.utmstack.loggin.filter.MdcCleanupFilter;
67
import org.slf4j.LoggerFactory;
78
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
10+
import org.springframework.context.annotation.Bean;
811
import org.springframework.context.annotation.Configuration;
912
import tech.jhipster.config.JHipsterProperties;
1013

@@ -45,4 +48,13 @@ public LoggingConfiguration(
4548
addContextListener(context, customFields, loggingProperties);
4649
}
4750
}
51+
52+
@Bean
53+
public FilterRegistrationBean<MdcCleanupFilter> mdcCleanupFilter() {
54+
FilterRegistrationBean<MdcCleanupFilter> registrationBean = new FilterRegistrationBean<>();
55+
registrationBean.setFilter(new MdcCleanupFilter());
56+
registrationBean.setOrder(Integer.MAX_VALUE);
57+
registrationBean.addUrlPatterns("/*");
58+
return registrationBean;
59+
}
4860
}

0 commit comments

Comments
 (0)