ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Filter Logging- Spring Filter Hands On 4
    Spring Framework/Filter 2023. 10. 15. 00:01

    개요

    Spring Filter를 활용해서 Response, Request에 대한 로깅을 수행해보고자 합니다.

     

    시작 전 주의사항

    HttpServletRequest, HttpServletResponse의 경우에 내부적으로 Stream을 사용하기 때문에 ContentCachingRequestWrapper와 ContentCachingResponseWrapper를 사용해주어야 합니다.

     

    Stream은 한번 소비하면 재사용할 수 없습니다.

     

    Response Request Logging

    class ResponseRequestLoggingFilter : OncePerRequestFilter() {
    
        override fun doFilterInternal(
            request: HttpServletRequest,
            response: HttpServletResponse,
            filterChain: FilterChain
        ) {
            val requestWrapper = ContentCachingRequestWrapper(request)
            val responseWrapper = ContentCachingResponseWrapper(response)
            val start = System.currentTimeMillis()
            filterChain.doFilter(requestWrapper, responseWrapper)
            val end = System.currentTimeMillis()
    
            com.example.study.log.logger.info{
                """
                    "[REQUEST] ${request.method} - ${request.requestURI} ${responseWrapper.status} - ${(end - start) / 1000.0}
                    "Headers : ${getHeaders(request)}
                    "Request : ${getRequestBody(requestWrapper)}
                    "Response : ${getResponseBody(responseWrapper)}
                """.trimIndent()
            }
        }
    
        private fun getHeaders(request: HttpServletRequest): Map<Any, Any> {
            val headerMap: MutableMap<Any, Any> = HashMap<Any, Any>()
            val headerArray: Enumeration<*> = request.headerNames
            while (headerArray.hasMoreElements()) {
                val headerName = headerArray.nextElement() as String
                headerMap[headerName] = request.getHeader(headerName)
            }
            return headerMap
        }
    
        private fun getRequestBody(request: ContentCachingRequestWrapper): String {
            val wrapper = WebUtils.getNativeRequest(
                request,
                ContentCachingRequestWrapper::class.java
            )
            if (wrapper != null) {
                val buf = wrapper.contentAsByteArray
                if (buf.size > 0) {
                    try {
                        return String(buf, 0, buf.size, charset(wrapper.characterEncoding))
                    } catch (e: UnsupportedEncodingException) {
                        return " - "
                    }
                }
            }
            return " - "
        }
    
        @Throws(IOException::class)
        private fun getResponseBody(response: HttpServletResponse): String {
            var payload: String? = null
            val wrapper = WebUtils.getNativeResponse(
                response,
                ContentCachingResponseWrapper::class.java
            )
            if (wrapper != null) {
                val buf = wrapper.contentAsByteArray
                if (buf.size > 0) {
                    payload = String(buf, 0, buf.size, charset(wrapper.characterEncoding))
                    wrapper.copyBodyToResponse()
                }
            }
            return payload ?: " - "
        }
    }

     

    여러 가지 Controller를 구현하여 테스트

    @GetMapping("/path-and-header-logging/{user-id}")
    fun pathVariableAndHeaderLogging(@RequestHeader role: String, @PathVariable("user-id") id: String): ResponseEntity<TestDTO> {
        return ResponseEntity.ok(
            TestDTO(
                role = role,
                id = id,
            )
        )
    }
    
    ### 로깅 테스트
    GET localhost:8080/path-and-header-logging/1234
    role: admin

    로그 결과

    @PostMapping("/request-body")
    fun responseEntity(@RequestBody testDTO: TestDTO): String{
        println(testDTO)
        return "ok"
    }
    
    ### 로깅 테스트
    POST localhost:8080/request-body
    content-type: application/json
    role: admin
    
    {
      "id": "1234",
      "role": "user"
    }

    로그 결과

    @GetMapping("/exception-log")
    fun illegalArgumentExceptionLogging() {
        throw IllegalArgumentException("컨트롤러로 들어와서 예외발생")
    }
    
    ### 로깅 예외 테스트
    GET localhost:8080/exception-log
    role: admin

    로그 결과

    마무리

    @RestControllerAdvice
    class GlobalExceptionHandler {
        @ExceptionHandler(Exception::class)
        fun globalException(e: Exception): ResponseEntity<String> {
            return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(e.message)
        }
    }

    위의 방법으로 구현하면 FilterChain 과정에서 예외가 발생하는 경우에는 Filter Logging이 남지 않습니다.

    이런 경우에는 try-catch나 runCatching으로 잡아서 해결해주어야 합니다.

    또한 컨트롤러에 진입하여 예외가 발생하는 경우 ControllerAdvice에서 예외 메시지를 response Body로 전달해 주어야 합니다.

     

    response에 예외 메시지가 출력됩니다.

    댓글

Designed by Tistory.