diff --git a/build.gradle b/build.gradle
index af94b27..e60d0f7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -26,6 +26,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
+ implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
diff --git a/src/main/java/com/deare/backend/global/aop/LoggingAspect.java b/src/main/java/com/deare/backend/global/aop/LoggingAspect.java
new file mode 100644
index 0000000..599d53a
--- /dev/null
+++ b/src/main/java/com/deare/backend/global/aop/LoggingAspect.java
@@ -0,0 +1,34 @@
+package com.deare.backend.global.aop;
+
+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.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Aspect
+@Component
+public class LoggingAspect {
+
+ @Around("execution(* com.deare.backend.api..*Service*.*(..))")
+ public Object logServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
+ MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+ String className = joinPoint.getTarget().getClass().getSimpleName();
+ String methodName = signature.getName();
+
+ long start = System.currentTimeMillis();
+
+ try {
+ Object result = joinPoint.proceed();
+ long elapsed = System.currentTimeMillis() - start;
+ log.info("[{}] {} - {}ms", className, methodName, elapsed);
+ return result;
+ } catch (Exception e) {
+ long elapsed = System.currentTimeMillis() - start;
+ log.error("[{}] {} - {}ms - 예외: {}", className, methodName, elapsed, e.getMessage());
+ throw e;
+ }
+ }
+}
diff --git a/src/main/java/com/deare/backend/global/filter/MDCLoggingFilter.java b/src/main/java/com/deare/backend/global/filter/MDCLoggingFilter.java
new file mode 100644
index 0000000..e2641d4
--- /dev/null
+++ b/src/main/java/com/deare/backend/global/filter/MDCLoggingFilter.java
@@ -0,0 +1,48 @@
+package com.deare.backend.global.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.MDC;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.UUID;
+
+@Component
+public class MDCLoggingFilter extends OncePerRequestFilter {
+
+ private static final String REQUEST_ID = "requestId";
+ private static final String USER_ID = "userId";
+ private static final String METHOD = "method";
+ private static final String URI = "uri";
+
+ @Override
+ protected void doFilterInternal(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain
+ ) throws ServletException, IOException {
+
+ try {
+ MDC.put(REQUEST_ID, UUID.randomUUID().toString().substring(0, 8));
+ MDC.put(METHOD, request.getMethod());
+ MDC.put(URI, request.getRequestURI());
+
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth != null && auth.getPrincipal() instanceof Long userId) {
+ MDC.put(USER_ID, String.valueOf(userId));
+ } else {
+ MDC.put(USER_ID, "anonymous");
+ }
+
+ filterChain.doFilter(request, response);
+ } finally {
+ MDC.clear();
+ }
+ }
+}
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
index c17a845..e0abdf4 100644
--- a/src/main/resources/logback-spring.xml
+++ b/src/main/resources/logback-spring.xml
@@ -3,7 +3,16 @@
-
+
+
+
+
+
+ ${LOG_PATTERN}
+ UTF-8
+
+