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
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.runimo.runimo.common.log;

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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.runimo.runimo.common.log.model.HttpRequestLogInfo;
import org.runimo.runimo.common.log.model.MethodEndLogInfo;
import org.runimo.runimo.common.log.model.MethodStartLogInfo;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

private final LogMessageFormatter logMessageFormatter;

@Pointcut("execution(* org.runimo.runimo..controller.*Controller.*(..))")
private void controller() {
}

@Before("controller()")
public void apiRequestLogger() {
ServletRequestAttributes attributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
if (attributes == null) {
log.info("ServletRequestAttributes is null");
return;
}

HttpServletRequest request = attributes.getRequest();
HttpRequestLogInfo logInfo = HttpRequestLogInfo.of(request);

log.info(logMessageFormatter.toHttpRequestLogMessage(logInfo));
}

}
108 changes: 108 additions & 0 deletions src/main/java/org/runimo/runimo/common/log/LogMessageFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.runimo.runimo.common.log;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.runimo.runimo.common.log.model.HttpRequestLogInfo;
import org.runimo.runimo.common.log.model.MethodEndLogInfo;
import org.runimo.runimo.common.log.model.MethodStartLogInfo;
import org.springframework.stereotype.Component;

@Component
public class LogMessageFormatter { // TODO : 중복코드 리팩토링

public String toHttpRequestLogMessage(HttpRequestLogInfo logInfo) {
String queryParamString = convertMapToLogFormatString(logInfo.queryParams(),
new StringBuilder()).toString();

Map<String, String> logs = new LinkedHashMap<>();
logs.put("method", logInfo.requestMethod());
logs.put("uri", logInfo.uri());
logs.put("query_params", queryParamString);
logs.put("time", getCurrentTime());

StringBuilder sb = new StringBuilder();
sb.append("HTTP_REQUEST ");

convertMapToLogFormatString(logs, sb);

return sb.toString();
}


public String toMethodStartLogMessage(MethodStartLogInfo logInfo) {
String paramString = convertMapToLogFormatString(logInfo.params(),
new StringBuilder()).toString();

Map<String, String> logs = new LinkedHashMap<>();
logs.put("name", logInfo.className() + "." + logInfo.methodName());
logs.put("authenticated", String.valueOf(logInfo.authenticated()));
logs.put("user_id", logInfo.userId());
logs.put("params", paramString);
logs.put("time", getCurrentTime());

StringBuilder sb = new StringBuilder();
sb.append("METHOD_CALL ");

convertMapToLogFormatString(logs, sb);

return sb.toString();
}

public String toMethodEndLogMessage(MethodEndLogInfo logInfo) {
Map<String, String> logs = new LinkedHashMap<>();
logs.put("name", logInfo.className() + "." + logInfo.methodName());
logs.put("elapsed_time", logInfo.elapsedTimeMillis() + "ms");
logs.put("return", logInfo.returnData());
logs.put("time", getCurrentTime());

StringBuilder sb = new StringBuilder();
sb.append("METHOD_END ");

convertMapToLogFormatString(logs, sb);

return sb.toString();
}

public String toMethodErrorLogMessage(Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append("METHOD_EXCEPTION ");
sb.append(ex.getMessage());

return sb.toString();
}
Comment on lines +70 to +76
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error log format to include more debug information

The current method error log only includes the exception message. For effective debugging, include the exception type and method information as well.

Improve the exception logging by adding more context:

public String toMethodErrorLogMessage(Throwable ex) {
    StringBuilder sb = new StringBuilder();
    sb.append("METHOD_EXCEPTION ");
-    sb.append(ex.getMessage());
+    sb.append("[exception_type=").append(ex.getClass().getName());
+    sb.append(", message=").append(ex.getMessage());
+    sb.append("]");

    return sb.toString();
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public String toMethodErrorLogMessage(Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append("METHOD_EXCEPTION ");
sb.append(ex.getMessage());
return sb.toString();
}
public String toMethodErrorLogMessage(Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append("METHOD_EXCEPTION ");
sb.append("[exception_type=").append(ex.getClass().getName());
sb.append(", message=").append(ex.getMessage());
sb.append("]");
return sb.toString();
}


private StringBuilder convertMapToLogFormatString(Map<String, String> infoMap,
StringBuilder sb) {
sb.append("[");

Iterator<Entry<String, String>> it = infoMap.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> entry = it.next();
sb.append(convertCamelCaseToSnakeCase(entry.getKey()));
sb.append("=");
sb.append(entry.getValue());

if (it.hasNext()) {
sb.append(", ");
}
}

sb.append("]");
return sb;
}

private String getCurrentTime() {
return ZonedDateTime.now().toString();
}

private String convertCamelCaseToSnakeCase(String camelCase) {
return camelCase
.replaceAll("([A-Z])(?=[A-Z])", "$1_")
.replaceAll("([a-z])([A-Z])", "$1_$2")
.toLowerCase();
}
}
66 changes: 66 additions & 0 deletions src/main/java/org/runimo/runimo/common/log/MethodLogAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.runimo.runimo.common.log;

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.runimo.runimo.common.log.model.MethodEndLogInfo;
import org.runimo.runimo.common.log.model.MethodStartLogInfo;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

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

private final LogMessageFormatter logMessageFormatter;

@Pointcut("@within(org.runimo.runimo.common.log.ServiceLog) || @annotation(org.runimo.runimo.common.log.ServiceLog)")
private void annotatedClassAndMethod() {
}

@Around("annotatedClassAndMethod()")
public Object calledMethodLogger(ProceedingJoinPoint pjp) throws Throwable {
MethodStartLogInfo methodStartLogInfo = getMethodStartLogInfo(pjp);
log.info(logMessageFormatter.toMethodStartLogMessage(methodStartLogInfo));

long startTime = getCurrentTimeMillis();
long endTime;
Object proceedReturn = null;
try {
proceedReturn = pjp.proceed();
} catch (Throwable ex) {
log.error(logMessageFormatter.toMethodErrorLogMessage(ex));
}
endTime = getCurrentTimeMillis();

MethodEndLogInfo methodEndLogInfo = MethodEndLogInfo.of(pjp, endTime - startTime,
proceedReturn);
log.info(logMessageFormatter.toMethodEndLogMessage(methodEndLogInfo));

return proceedReturn;
}

private static MethodStartLogInfo getMethodStartLogInfo(ProceedingJoinPoint pjp) {
MethodStartLogInfo methodStartLogInfo;

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
methodStartLogInfo = MethodStartLogInfo.of(pjp, true, authentication.getName());
} else {
methodStartLogInfo = MethodStartLogInfo.of(pjp, false, null);
}

return methodStartLogInfo;
}

private long getCurrentTimeMillis() {
return System.currentTimeMillis();
}

}
15 changes: 15 additions & 0 deletions src/main/java/org/runimo/runimo/common/log/ServiceLog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.runimo.runimo.common.log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 비즈니스 로그 기록 어노테이션 - 메서드 실행 시 메서드명, 인자 목록, 반환값, 소요시간 등의 정보를 로깅
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceLog {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.runimo.runimo.common.log.model;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import lombok.AccessLevel;
import lombok.Builder;

@Builder(access = AccessLevel.PRIVATE)
public record HttpRequestLogInfo(
String requestMethod,
String uri,
Map<String, String> queryParams
) {

public static HttpRequestLogInfo of(HttpServletRequest request) {
String queryString = request.getQueryString();

Map<String, String> queryParams = getQueryParamMap(queryString);

return HttpRequestLogInfo.builder()
.requestMethod(request.getMethod())
.uri(request.getRequestURI())
.queryParams(queryParams)
.build();
}

private static Map<String, String> getQueryParamMap(String queryString) {
if (queryString == null || queryString.isBlank()) {
return Collections.emptyMap();
}

Map<String, String> queryParams = new HashMap<>();

String[] paramPairs = queryString.split("&");
for (String paramPair : paramPairs) {
String[] keyVal = paramPair.split("=", 2);

String val = keyVal.length < 2 ? "" : keyVal[1];
queryParams.put(keyVal[0], val);
}

return queryParams;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.runimo.runimo.common.log.model;

import lombok.AccessLevel;
import lombok.Builder;
import org.aspectj.lang.JoinPoint;

@Builder(access = AccessLevel.PRIVATE)
public record MethodEndLogInfo(
String className,
String methodName,
long elapsedTimeMillis,
String returnData
) {

public static MethodEndLogInfo of(JoinPoint joinPoint, long elapsedTimeMillis,
Object returnData) {
return MethodEndLogInfo.builder()
.className(getClassName(joinPoint))
.methodName(joinPoint.getSignature().getName())
.elapsedTimeMillis(elapsedTimeMillis)
.returnData(String.valueOf(returnData))
.build();
}

private static String getClassName(JoinPoint joinPoint) {
String classPath = joinPoint.getSignature().getDeclaringTypeName();
return classPath.substring(classPath.lastIndexOf(".") + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.runimo.runimo.common.log.model;

import java.util.HashMap;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Builder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

@Builder(access = AccessLevel.PRIVATE)
public record MethodStartLogInfo(
String className,
String methodName,
boolean authenticated,
String userId,
Map<String, String> params
) {

public static MethodStartLogInfo of(JoinPoint joinPoint, boolean authenticated, String userId) {
String className = getClassName(joinPoint);

Map<String, String> params = getParamMap(joinPoint);

return MethodStartLogInfo.builder()
.className(className)
.methodName(joinPoint.getSignature().getName())
.authenticated(authenticated)
.userId(userId)
.params(params)
.build();
}

private static Map<String, String> getParamMap(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();

Map<String, String> params = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
params.put(parameterNames[i], String.valueOf(parameterValues[i]));
}
return params;
}

private static String getClassName(JoinPoint joinPoint) {
String classPath = joinPoint.getSignature().getDeclaringTypeName();
return classPath.substring(classPath.lastIndexOf(".") + 1);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.runimo.runimo.common.response;

import lombok.ToString;
import org.runimo.runimo.exceptions.code.CustomResponseCode;

@ToString
public class ErrorResponse extends Response {

private ErrorResponse(final String errorMessage, final String errorCode) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/runimo/runimo/common/response/Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.ToString;
import org.runimo.runimo.exceptions.code.CustomResponseCode;

@ToString
@Getter
public class Response {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.runimo.runimo.common.response;

import lombok.Getter;
import lombok.ToString;
import org.runimo.runimo.exceptions.code.CustomResponseCode;

@ToString
@Getter
public class SuccessResponse<T> extends Response {

Expand Down
Loading
Loading