제출용 트러블 슈팅

제이 용·7일 전

트러블 슈팅(1)

ArgumentResolver등록이 안되는 문제 발생

  • 코드
package org.example.expert.config;

import lombok.RequiredArgsConstructor;
import org.example.expert.domain.common.interceptor.adminCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final adminCheckInterceptor adminCheckInterceptor;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthUserArgumentResolver());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(adminCheckInterceptor)
                .addPathPatterns("/admin/users/*")
                .addPathPatterns("/admin/comments/*");
    }

}
  • 오류 코드를 봤을 때 resolver.add(A) A에 들어가는 값이 null 값처리가 된다는 문구를 확인하였고 resolver는 아무 문제가 없었다.

  • 한참을 씨름을 하다가 Webconfig 클래스에 @Configuration 어노테이션이 빠져있는 것을 뒤늦게 확인..

  • 붙여주니 정상적으로 잘 돌아갔다. 빠진 것이 없는지 위에서부터 순차적으로 보는 것이 디버깅보다 더 첫번째 인 것 같다.

트러블 슈팅(2)

  • 인터셉터 구현 후 정상 실행이 되는지 테스트를 진행하였고, 로그가 안찍히는 것이 확인이 되었다.

  • 튜터님의 세션 덕분에 필터를 거친 후 인터셉터로 넘어가기 때문에 필터에서 중복되는 것이 있기 때문에 인터셉터에서 확인이 불가능한 것인가 의심할 수 있게 되었다.

  • jwtFilter에 아니나 다를까 동일한 로직이 실행되고 있었고 그 부분을 주석 처리를 해 다시 확인해 보았다.

  • 그래도 나오지 않았다. 코드를 확인해보니

if(!UserRole.ADMIN.name().equals(role)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "권한이 없습니다");
            return false;
        }
  • ??? 로그 코드에 아예없다. 실수인건지 까먹은건지 로그를 쓰지도 않고 로그를 확인하고 있었다.
log.info("접근이 차단되었습니다.");
  • 이후 위 코드를 추가 후 정상 작동하는 것을 확인할 수 있었다..

  • 코드는 문제가 없다. 문제는 나에게 있다.ㅎㅎ

트러블 슈팅(3)

  • 도전과제 5Lv에 AOP를 구현하는 과정 속에 요구사항이 요청/응답의 바디값을 로그에 찍히게 하라는 사항이 있었다.
package org.example.expert.domain.common.AOP;

import com.fasterxml.jackson.databind.ObjectMapper;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

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

    private final ObjectMapper objectMapper;
//    .controller.*AdminController.*

    //전 후로 로그를 찍어준다.
    @Around("execution(* org.example.expert.domain.*.controller.*AdminController.*(..))")
    public Object logApi(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletResponseAttributes) ResponseContextHolder.currentResponseAttributes()).getResponse();

        ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;

        Long userId = (Long) request.getAttribute("userId");
        LocalDateTime now = LocalDateTime.now();
        String requestURI = request.getRequestURI();
        String resquestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        log.info("userId = {},Time = {}, URL = {}, body = {}", userId, now, requestURI, resquestBody);

        Object result = joinPoint.proceed();

        log.info("body = {}", response);

        return result;
    }
}
  • 최초 앞서 캐싱 필터를 통해 각 응답/요청을 적용 시키고, 적용 시킨 값을 로그에 찍히게 하려하였지만 응답의 바디 값이 계속 비어져 있는 것을 확인 할 수 있었다.

  • 팀원분과 상의하면서 의논해보니 AOP는 Controller → Service → AOP 순으로 실행되기 때문에 저 시점에는 아직 응답이 읽히지 않을 때이기 때문이란 것을 알게 되었다.

  • 따라서 응답값을 따로 빼주는 것이 아닌

package org.example.expert.domain.common.AOP;

import com.fasterxml.jackson.databind.ObjectMapper;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

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

    private final ObjectMapper objectMapper;
//    .controller.*AdminController.*

    //전 후로 로그를 찍어준다.
    @Around("execution(* org.example.expert.domain.*.controller.*AdminController.*(..))")
    public Object logApi(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;

        Long userId = (Long) request.getAttribute("userId");
        LocalDateTime now = LocalDateTime.now();
        String requestURI = request.getRequestURI();
        String resquestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
        log.info("userId = {},Time = {}, URL = {}, body = {}", userId, now, requestURI, resquestBody);

        Object result = joinPoint.proceed();

        log.info("body = {}", objectMapper.writeValueAsString(result));

        return result;
    }
}
  • 오브젝트매퍼를 활용해서 메서드의 결과값을 통해 응답값을 출력할 수 있도록 수정하니 잘 작동되는 것을 확인할 수 있었다.

  • 팀원들에게 문제를 공유하고 같이 해결하니 금방할 수 있게 되었다.

1개의 댓글

J없는 사람은 서러워서 살겠나요 ㅠㅠ 더블제이의 글에 감탄하고 갑니다 ㅠㅠ

답글 달기