Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ out/

### env file ###
*.env

### log file ###
/logs/
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {
// Monitoring
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'com.github.loki4j:loki-logback-appender:1.5.1'

//elastic-search
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
Expand Down Expand Up @@ -116,6 +117,7 @@ def jacocoExcludePatterns = [
'**/*Application.class',
'**/*Config*',
'**/*Exception*',
'**/*Extractor*',
'**/*Request*',
'**/*Response*',
'**/*Entity*',
Expand All @@ -135,6 +137,7 @@ def jacocoExcludePatternsForVerify = [
'*.*Application*',
'*.*Config*',
'*.*Exception*',
'*.*Extractor*',
'*.*Request*',
'*.*Response*',
'*.*Entity*',
Expand Down
70 changes: 0 additions & 70 deletions src/main/java/com/somemore/global/aspect/LoggingAspect.java

This file was deleted.

90 changes: 90 additions & 0 deletions src/main/java/com/somemore/global/aspect/log/LoggingAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.somemore.global.aspect.log;

import com.somemore.global.aspect.log.extractor.ParameterExtractor;
import com.somemore.global.aspect.log.extractor.RequestExtractor;
import com.somemore.global.aspect.log.extractor.ResponseExtractor;
import com.somemore.global.common.response.LoggedResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;


import java.util.UUID;

@RequiredArgsConstructor
@Slf4j
@Aspect
@Component
public class LoggingAspect {

private final RequestExtractor requestExtractor;
private final ResponseExtractor responseExtractor;
private final ParameterExtractor parameterExtractor;

@Pointcut("execution(* com.somemore.domains.*.controller..*.*(..))")
private void controllerPointCut() {}

@Around("controllerPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);

try {
return doLogAround(joinPoint);
} finally {
MDC.remove("requestId");
}
}

private Object doLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
HttpServletRequest request = requestExtractor.getCurrentRequest();

MDC.put("method", request.getMethod());
MDC.put("uri", request.getRequestURI());

String params = parameterExtractor.extractParameters(joinPoint);
log.info("엔드포인트 호출: {} \n- URI: {} \n- Method: {} \n- 파라미터: {}",
methodName,
request.getRequestURI(),
request.getMethod(),
params);

long startTime = System.currentTimeMillis();

try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;

LoggedResponse loggedResponse = responseExtractor.extractResponse(result);
log.info("호출 성공: {} \n- 응답 코드: {} \n- 응답 값: {} \n- 실행 시간: {}ms",
methodName,
loggedResponse.getStatusCode(),
loggedResponse.getBody(),
elapsedTime);

return result;
} catch (Exception e) {
long elapsedTime = System.currentTimeMillis() - startTime;
HttpStatus status = responseExtractor.extractExceptionStatus(e);

log.warn("예외 발생: {} \n- 예외 코드: {} \n- 예외 타입: {} \n- 예외 메세지: {} \n- 실행 시간: {}ms",
methodName,
status,
e.getClass().getSimpleName(),
e.getMessage(),
elapsedTime);

throw e;
} finally {
MDC.clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.somemore.global.aspect.log.extractor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.somemore.global.aspect.log.utils.SensitiveDataMasker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

import java.lang.reflect.Parameter;
import java.util.LinkedHashMap;
import java.util.Map;

@RequiredArgsConstructor
@Slf4j
@Component
public class ParameterExtractor {

private final ObjectMapper objectMapper;
private final SensitiveDataMasker sensitiveDataMasker;

public String extractParameters(ProceedingJoinPoint joinPoint) {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Parameter[] parameters = signature.getMethod().getParameters();
Object[] args = joinPoint.getArgs();

if (parameters.length == 0) {
return "{}";
}

Map<String, Object> paramMap = new LinkedHashMap<>();
for (int i = 0; i < parameters.length; i++) {
if (args[i] != null) {
addParameter(paramMap, parameters[i], args[i]);
}
}

return objectMapper.writeValueAsString(paramMap);
} catch (Exception e) {
log.warn("파라미터 변환 실패: {}", e.getMessage());
return "{}";
}
}

private void addParameter(Map<String, Object> paramMap, Parameter parameter, Object value) throws JsonProcessingException {
String paramName = extractParamName(parameter);
paramMap.put(paramName, sensitiveDataMasker.maskSensitiveData(paramName, value, objectMapper));
}

private String extractParamName(Parameter parameter) {
PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
if (pathVariable != null && !pathVariable.value().isEmpty()) {
return pathVariable.value();
}

RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && !requestParam.value().isEmpty()) {
return requestParam.value();
}

return parameter.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.somemore.global.aspect.log.extractor;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
public class RequestExtractor {

public HttpServletRequest getCurrentRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new IllegalStateException("요청을 찾을수 없습니다.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오타 발견했습니다 ㅎㅎ

throw new IllegalStateException("요청을 찾을수 없습니다.");
throw new IllegalStateException("요청을 찾을 수 없습니다.");

}
return attributes.getRequest();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.somemore.global.aspect.log.extractor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.somemore.global.common.response.LoggedResponse;
import com.somemore.global.exception.DuplicateException;
import com.somemore.global.exception.ImageUploadException;
import com.somemore.global.exception.BadRequestException;
import com.somemore.global.exception.NoSuchElementException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;

@RequiredArgsConstructor
@Slf4j
@Component
public class ResponseExtractor {

private final ObjectMapper objectMapper;

public LoggedResponse extractResponse(Object result) {
try {
if (result == null) {
return new LoggedResponse(HttpStatus.OK, "null");
}

if (result instanceof ResponseEntity<?> responseEntity) {
String body = objectMapper.writeValueAsString(responseEntity.getBody());
return new LoggedResponse(responseEntity.getStatusCode(), body);
}

return new LoggedResponse(HttpStatus.OK, objectMapper.writeValueAsString(result));
} catch (Exception e) {
log.warn("응답 변환 실패: {}", e.getMessage());
return new LoggedResponse(HttpStatus.OK, "[응답 변환 실패]");
}
}

public HttpStatus extractExceptionStatus(Exception e) {
if (e instanceof BadRequestException ||
e instanceof ImageUploadException ||
e instanceof DuplicateException ||
e instanceof MethodArgumentNotValidException) {
return HttpStatus.BAD_REQUEST;
} else if (e instanceof NoSuchElementException) {
return HttpStatus.NOT_FOUND;
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
Comment on lines +41 to +51
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상위 커스텀 예외 클래스를 활용하는 방안도 있을 것 같습니다.
더 확장될 일이 없다면 지금도 좋아보입니다!

또, BadRequestException 예외가 너무 넓은 부분을 처리하지 않는지도 생각해볼 수 있을 것 같습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

또, BadRequestException 예외가 너무 넓은 부분을 처리하지 않는지도 생각해볼 수 있을 것 같습니다.

BadReq와 여러 Exception을 포함해서 BadReq status 를 반환하는 것에서 찝찝함을 느껴서 코멘트했습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이제 확인했네요 죄송합니다
저도 어색하다고 생각한부분이라 To do 적어놓고 지금 작업 완료후에 바꿔보겠습니다

}
Loading
Loading