Spring Framework/Filter
Spring Filter Logging- Spring Filter Hands On 4
Junuuu
2023. 10. 15. 00:01
728x90
개요
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에 예외 메시지가 출력됩니다.