BootCamp 53day

GyeongNamΒ·2024λ…„ 1μ›” 29일
0

BootCamp

λͺ©λ‘ 보기
47/49
post-thumbnail

πŸ“… 2024λ…„ 01μ›” 29일


53일차 : Spring (13)

Springμ—μ„œ Log

  • application.yml
# trace < debug < info < error 순으둜 둜그레벨λ₯Ό 가짐
# info μ„€μ • μ‹œ  trace, debug λ‘œκ·ΈλŠ” 좜λ ₯λ˜μ§€ μ•ŠμŒ
logging:
  # 둜그파일 κ²½λ‘œμ™€ 파일λͺ… 지정
  file:
    name: logs/app.log
  level:
    root: info
  • LogTestController
/*
@Slf4j λ₯Ό 톡해 logBack 둜그라이브러리 μ‚¬μš©κ°€λŠ₯
 */
@Slf4j
@RestController
public class LogTestController {
    /*
    @Slf4jλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³ , 직접 라이브러리 import에 둜거 생성가λŠ₯
     */
    // private  static final Logger loger = LoggerFactory.getLogger("logger");
    @GetMapping("/log/test1")
    public String testMethod1(){
        log.debug("디버그 λ‘œκ·Έμž…λ‹ˆλ‹€.");
        log.info("인포 λ‘œκ·Έμž…λ‹ˆλ‹€.");
        log.error("μ—λŸ¬ λ‘œκ·Έμž…λ‹ˆλ‹€.");
        return "ok";
    }
}
  • λ‘œκΉ… μ‚¬μš© 이유
λͺ©μ  μ„€λͺ…
디버깅 및 문제 ν•΄κ²° μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 문제λ₯Ό μ‹λ³„ν•˜κ³  ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 둜그λ₯Ό μ‚¬μš©. λ³€μˆ˜ κ°’, λ©”μ†Œλ“œ 호좜, μ˜ˆμ™Έ μŠ€νƒ 트레이슀 등이 기둝됨.
μ„±λŠ₯ λͺ¨λ‹ˆν„°λ§ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ„±λŠ₯을 μΆ”μ ν•˜κ³  λͺ¨λ‹ˆν„°λ§ν•˜κΈ° μœ„ν•΄ 둜그λ₯Ό μ‚¬μš©. λ©”μ†Œλ“œ μ‹€ν–‰ μ‹œκ°„, λ°μ΄ν„°λ² μ΄μŠ€ 쿼리 μ†Œμš” μ‹œκ°„ 등이 기둝될 수 있음.
감사 및 λ³΄μ•ˆ μ‚¬μš©μžμ˜ ν™œλ™ 좔적, λ³΄μ•ˆ 이벀트 λ‘œκΉ… 등을 톡해 감사 및 λ³΄μ•ˆ λͺ©μ μœΌλ‘œ μ‚¬μš©λ¨.
운영 및 λͺ¨λ‹ˆν„°λ§ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 운영 μƒνƒœλ₯Ό μΆ”μ ν•˜κ³  λͺ¨λ‹ˆν„°λ§ν•˜κΈ° μœ„ν•΄ 둜그λ₯Ό μ‚¬μš©. 쀑앙 μ§‘μ€‘μ‹μœΌλ‘œ λͺ¨λ‹ˆν„°λ§λ  수 있음.
  • log 파일 관리(logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--CONSOLE 둜그 κ΄€λ ¨ μ„€μ •-->
    <appender name = "CONSOLE" class ="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %logger{36} - %msg%n </pattern>
        </encoder>
    </appender>
    <!--μ •μƒλ‘œκ·Έ κ΄€λ ¨ μ„€μ • -->
    <!--λ‘œκΉ…μ—μ„œ rolling μ΄λΌλŠ” 것은 λ‚ μ§œμ˜ 변함에 따라 νŒŒμΌμ„ μƒˆλ‘­κ²Œ λ§Œλ“€μ–΄μ£ΌλŠ” 것 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %logger{36} - %msg%n </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd HH:mm:ss}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!--μ—λŸ¬λ‘œκ·Έ κ΄€λ ¨ μ„€μ • -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>logs/app-error.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %logger{36} - %msg%n </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app-error.%d{yyyy-MM-dd HH:mm:ss}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
<!--info 둜그의 경우 정상 log파일둜 뢄리-->
    <root level = "info">
    <!--console κ³Ό 정상 둜그파일레 λ‘œκ·Έλ‚΄μš© μΆ”κ°€-->
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
<!--error 둜그의 경우 μ—λŸ¬ log 파일둜 뢄리-->
<!--    <logger level = "error" name="com.encore.board">-->
<!--        <appender-ref ref="ERROR_FILE"/>-->
<!--    </logger>-->
</configuration>

AOP (관점 지ν–₯ ν”„λ‘œκ·Έλž˜λ°)

μš©μ–΄/κ°œλ… λ‚΄μš©
Aspect (관점) 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œ λ°œμƒν•˜λŠ” νš‘λ‹¨ 관심사λ₯Ό λͺ¨λ“ˆν™”ν•œ κ²ƒμœΌλ‘œ, 예λ₯Ό λ“€μ–΄ λ‘œκΉ…, νŠΈλžœμž­μ…˜ 처리, λ³΄μ•ˆ 등이 해당됨.
Advice (μ‘°μ–Έ) μ–΄λŠ μ‹œμ μ—μ„œ 어디에 관점을 μ μš©ν• μ§€λ₯Ό μ •μ˜ν•œ κ²ƒμœΌλ‘œ, λ©”μ†Œλ“œ 호좜 μ „, ν›„, μ˜ˆμ™Έ λ°œμƒ μ‹œ λ“±κ³Ό 같은 μ§€μ μ—μ„œ 적용될 수 있음.
Pointcut (포인트컷) μ–΄λ–€ μ§€μ μ—μ„œ μ–΄λ–€ 관점을 μ μš©ν• μ§€λ₯Ό μ •μ˜ν•œ κ²ƒμœΌλ‘œ, λ©”μ†Œλ“œ 호좜, 객체 생성, ν•„λ“œ μ ‘κ·Ό λ“± λ‹€μ–‘ν•œ 지점을 지정할 수 있음.
Join Point (쑰인 포인트) ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 νŠΉμ • μ‹œμ μœΌλ‘œ, 관점이 μ μš©λ˜μ–΄μ•Ό ν•˜λŠ” νŠΉμ • 지점을 λ‚˜νƒ€λƒ„. λ©”μ†Œλ“œ 호좜, 객체 생성 등이 쑰인 ν¬μΈνŠΈμ— ν•΄λ‹Ήν•  수 있음.
Weaving (μœ„λΉ™) 관점을 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— μ μš©ν•˜λŠ” κ³Όμ •μœΌλ‘œ, μ½”λ“œμ— 관점을 μ‚½μž…ν•˜κ±°λ‚˜ μ—°κ²°ν•˜μ—¬ μ‹€ν–‰λ˜λ„λ‘ λ§Œλ“œλŠ” 것을 μ˜λ―Έν•¨.
  • AopLogService
@Slf4j
@Aspect
@Component
public class AopLogService {
    /*
    aop 에 λŒ€μƒμ΄ λ˜λŠ” controller, service 등을 μ •μ˜
    @Pointcut("execution(* com.encore.board..controller..*.*(..))")
     */
    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controllerPointCut(){
    }
    // 방식 1
    @Before("controllerPointCut()")
    public void beforeController(JoinPoint joinPoint){
        log.info("Before Controller");
        /*
        λ©”μ„œλ“œκ°€ μ‹€ν–‰λ˜κΈ° 전에 인증, μž…λ ₯κ°’ 검증등을 μˆ˜ν–‰ν•˜λŠ” μš©λ„λ‘œ μ‚¬μš©ν•˜λŠ” 사전단계
         */
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        /*
        json ν˜•νƒœλ‘œ μ‚¬μš©μžμ˜ μš”μ²­μ„ μ‘°λ¦½ν•˜κΈ° μœ„ν•œ 둜직
         */
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode objectNode = objectMapper.createObjectNode();
        objectNode.put("Method Name",joinPoint.getSignature().getName());
        objectNode.put("CRUD Name", httpServletRequest.getMethod());
        Map<String, String[]> paramMap = httpServletRequest.getParameterMap();
        ObjectNode objectNodeDetail = objectMapper.valueToTree(paramMap);
        objectNode.set("user inputs", objectNodeDetail);
        log.info("user request info" + objectNode);
    }
    @After("controllerPointCut()")
    public void afterController(){
        log.info("end Controller");
    }

    // 방식 2
    @Around("controllerPointCut()")
    public Object controllerLogger(ProceedingJoinPoint proceedingJoinPoint){ // joinPoint λž€ aop λŒ€μƒμœΌλ‘œ ν•˜λŠ” 컨트둀러의 νŠΉμ • λ©”μ„œλ“œλ₯Ό 의미
        // log.info("request method"+ proceedingJoinPoint.getSignature().toString());
        /*
        μ‚¬μš©μžμ˜ μš”μ²­κ°’μ„ json ν˜•νƒœλ‘œ 좜λ ₯ν•˜κΈ° μœ„ν•΄ HttpServletRequest 객체λ₯Ό κΊΌλ‚΄λŠ” 둜직
         */
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        /*
        json ν˜•νƒœλ‘œ μ‚¬μš©μžμ˜ μš”μ²­μ„ μ‘°λ¦½ν•˜κΈ° μœ„ν•œ 둜직
         */
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode objectNode = objectMapper.createObjectNode();
        objectNode.put("Method Name",proceedingJoinPoint.getSignature().getName());
        objectNode.put("CRUD Name", httpServletRequest.getMethod());
        Map<String, String[]> paramMap = httpServletRequest.getParameterMap();
        ObjectNode objectNodeDetail = objectMapper.valueToTree(paramMap);
        objectNode.set("user inputs", objectNodeDetail);
        log.info("user request info" + objectNode);
        try {
            return proceedingJoinPoint.proceed();
        }catch (Throwable e){
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
    }
}
μ–΄λ…Έν…Œμ΄μ…˜ μ“°μž„μ„Έ λ‚΄μš©
@Pointcut Pointcut을 μ •μ˜ν•˜λŠ” 데 μ‚¬μš© νŠΉμ • 지점(λ©”μ†Œλ“œ 호좜, 객체 생성 λ“±)에 AOPλ₯Ό μ μš©ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” ν‘œν˜„μ‹μ„ μ •μ˜ν•©λ‹ˆλ‹€. ν•΄λ‹Ή ν‘œν˜„μ‹μ€ μ—¬λŸ¬ μ–΄λ“œλ°”μ΄μŠ€μ—μ„œ 곡유될 수 μžˆμŠ΅λ‹ˆλ‹€.
@Before λ©”μ†Œλ“œ μ‹€ν–‰ 전에 μ–΄λ“œλ°”μ΄μŠ€λ₯Ό μ‹€ν–‰ μ–΄λ“œλ°”μ΄μŠ€ λ©”μ†Œλ“œλ₯Ό νŠΉμ • 쑰인 포인트(λ©”μ†Œλ“œ 호좜 λ“±) 전에 μ‹€ν–‰ν•©λ‹ˆλ‹€. 주둜 μ „μ²˜λ¦¬ λ‘œμ§μ„ μˆ˜ν–‰ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.
@After λ©”μ†Œλ“œ μ‹€ν–‰ 후에 μ–΄λ“œλ°”μ΄μŠ€λ₯Ό μ‹€ν–‰ μ–΄λ“œλ°”μ΄μŠ€ λ©”μ†Œλ“œλ₯Ό νŠΉμ • 쑰인 포인트 후에 μ‹€ν–‰ν•©λ‹ˆλ‹€. 주둜 ν›„μ²˜λ¦¬ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κ±°λ‚˜ μ˜ˆμ™Έ μ²˜λ¦¬μ— μ‚¬μš©λ©λ‹ˆλ‹€.
@Around λ©”μ†Œλ“œ μ‹€ν–‰ 전후에 μ–΄λ“œλ°”μ΄μŠ€λ₯Ό μ‹€ν–‰ μ–΄λ“œλ°”μ΄μŠ€ λ©”μ†Œλ“œκ°€ 쑰인 포인트λ₯Ό κ°μ‹Έμ„œ μ‹€ν–‰ 전후에 λ‘œμ§μ„ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ°€μž₯ μœ μ—°ν•œ μ–΄λ“œλ°”μ΄μŠ€λ‘œ, μ „, ν›„ 처리 및 μ˜ˆμ™Έ 처리 λ“± λͺ¨λ“  λ‘œμ§μ„ 포함할 수 μžˆμŠ΅λ‹ˆλ‹€.

@ControllerAdvice

: 컨트둀러 νŠΉν™”κΈ°λŠ₯ 주둜 μ˜ˆμ™Έμ²˜λ¦¬

  • CommonException
@Slf4j
@ControllerAdvice
public class CommonException {
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<Map<String, Object>> entityNotFoundExceptionHandler(EntityNotFoundException e){
        log.error("EntityNotFoundException message : " + e.getMessage());
        return this.responseErrorMassage(HttpStatus.NOT_FOUND,e.getMessage());
    }
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, Object>> IllegalArgumentExceptionHandler(IllegalArgumentException e){
        log.error("IllegalArgumentException : " + e.getMessage());
        return this.responseErrorMassage(HttpStatus.BAD_REQUEST,e.getMessage());
    }
    private ResponseEntity<Map<String, Object>> responseMassage(HttpStatus status, Object object){
        Map<String, Object> map = new HashMap<>();
        map.put("status", Integer.toString(status.value()));
        map.put("message", object);
        return new ResponseEntity<>(map, status);
    }
    private ResponseEntity<Map<String, Object>> responseErrorMassage(HttpStatus status, String message){
        Map<String, Object> map = new HashMap<>();
        map.put("status", Integer.toString(status.value()));
        map.put("status message", status.getReasonPhrase());
        map.put("error message", message);
        return new ResponseEntity<>(map, status);
    }
}

λ§Œμ•½ μ»¨νŠΈλ‘€λŸ¬μ—μ„œ try-catch 블둝 λ‚΄μ—μ„œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λ©΄ ν•΄λ‹Ή λ©”μ†Œλ“œμ˜ μ˜ˆμ™Έ 처리 둜직이 μš°μ„  호좜되고, μ²˜λ¦¬ν•˜μ§€ μ•Šκ±°λ‚˜ μ „νŒŒλ˜λŠ” κ²½μš°μ— @ControllerAdvice의 @ExceptionHandler λ©”μ†Œλ“œκ°€ 호좜


Spring μ‹€μŠ΅ github 링크

profile
503 Service Unavailable Error

0개의 λŒ“κΈ€