Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR :: 에러 로그, 응답 관련 리팩토링 #216

Merged
merged 7 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class RoleConverter : AttributeConverter<MutableList<Role>, String> {
Role.USER.name -> Role.USER
Role.ADMIN.name -> Role.ADMIN
Role.SELLER.name -> Role.SELLER
else -> throw CriticalException(500, "Role Convert Error")
else -> throw CriticalException("Role Convert Error")
}
}.toMutableList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.info.maeumgagym.common.exception

import com.info.maeumgagym.common.annotation.responsibility.UseCase
import com.info.maeumgagym.common.value.DomainNames
import org.springframework.http.HttpStatus

/**
* 마음가짐 내에서 전역적으로 쓰이는 예외 클래스의 최상위 타입
Expand Down Expand Up @@ -52,7 +53,8 @@ import com.info.maeumgagym.common.value.DomainNames
*/
open class MaeumGaGymException(
val status: Int,
message: String
final override val message: String,
val responseMessage: String = message
) : RuntimeException(message) {

companion object {
Expand All @@ -77,8 +79,9 @@ open class MaeumGaGymException(
*/
class BusinessLogicException(
status: Int,
message: String
) : MaeumGaGymException(status, message) {
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage) {

/**
* [message] 표준화를 위해 도메인 이름과 Http 상태 코드 메세지를 이용해 [message]를 작성하는 생성자
Expand Down Expand Up @@ -145,8 +148,9 @@ class BusinessLogicException(
*/
class SecurityException(
status: Int,
message: String
) : MaeumGaGymException(status, message) {
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage) {

constructor(errorCodePrefixSuffix: ErrorCodePrefixSuffix, domainName: DomainNames) :
this(
Expand Down Expand Up @@ -194,8 +198,9 @@ enum class ErrorCodePrefixSuffix(
*/
class FilterException(
status: Int,
message: String
) : MaeumGaGymException(status, message)
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage)

/**
* 인터셉터, 정확히는 커스텀 인터셉터 실행 중에 발생한 예외
Expand All @@ -204,21 +209,23 @@ class FilterException(
*/
class InterceptorException(
status: Int,
message: String
) : MaeumGaGymException(status, message)
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage)

/**
* 컨트롤러에서의 유효성 검증 과정 중 발생한 예외
*
* [ErrorLogResponseFilter]에서 Validation 실패시 발생하는 예외들을 이 예외로 변환 후 처리
* [com.info.maeumgagym.infrastructure.error.filter.ErrorLogResponseFilter]에서 Validation 실패시 발생하는 예외들을 이 예외로 변환 후 처리
*
* @see MaeumGaGymException
*/
class PresentationValidationException(
status: Int,
message: String,
val fields: Map<String, String>
) : MaeumGaGymException(status, message)
val fields: Map<String, String>,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage)

/**
* SecurityFilterChain 실행 중에 발생한 예외
Expand All @@ -227,35 +234,46 @@ class PresentationValidationException(
*/
class AuthenticationException(
status: Int,
message: String
) : MaeumGaGymException(status, message) {
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage) {

companion object {

// UnAuthorized
val INVALID_TOKEN get() = AuthenticationException(401, "Invalid Token")
val UNAUTHORIZED get() = AuthenticationException(401, "Unauthorized")
val EXPIRED_TOKEN get() = AuthenticationException(401, "Expired Token")
val WRONG_USER_TOKEN get() = SecurityException(401, "Issued And User Mismatch, Cannot Use It")
val NOT_A_MAEUMGAGYM_TOKEN get() = SecurityException(401, "It's Not a MaeumGaGym Authentication Token")
val WRONG_TYPE_TOKEN get() = SecurityException(401, "Token Type Wrong, Not Supported")
val REVOKED_TOKEN
get() = AuthenticationException(401, "Revoked Token.", INVALID_TOKEN.responseMessage)
val WRONG_USER_TOKEN
get() = AuthenticationException(
401,
"Issued And User Mismatch, Cannot Use It",
INVALID_TOKEN.responseMessage
)
val NOT_A_MAEUMGAGYM_TOKEN
get() = AuthenticationException(401, "It's Not a MaeumGaGym Authentication Token. Invalid Prefix")
val WRONG_TYPE_TOKEN
get() = AuthenticationException(401, "Token Type Wrong, Not Supported", INVALID_TOKEN.responseMessage)

// Forbidden
val ROLE_REQUIRED get() = AuthenticationException(403, "Role Required")
val ROLE_REQUIRED get() = AuthenticationException(403, "Role Required", FORBIDDEN.responseMessage)
}
}

class FeignException(
status: Int,
message: String
) : MaeumGaGymException(status, message) {
message: String,
responseMessage: String = message
) : MaeumGaGymException(status, message, responseMessage) {

companion object {

val FEIGN_BAD_REQUEST get() = FeignException(400, "Feign Bad Request")
val FEIGN_UNAUTHORIZED get() = FeignException(401, "Feign UnAuthorized")
val FEIGN_FORBIDDEN get() = FeignException(403, "Feign Forbidden")
val FEIGN_SERVER_ERROR get() = FeignException(500, "Feign Server Error")
val FEIGN_SERVER_ERROR get() = FeignException(500, "Feign Server Error", INTERNAL_SERVER_ERROR.responseMessage)
val FEIGN_UNKNOWN_CLIENT_ERROR get() = FeignException(500, "Feign Unknown Error")
}
}
Expand All @@ -266,6 +284,5 @@ class FeignException(
* @see MaeumGaGymException
*/
class CriticalException(
status: Int,
message: String
) : MaeumGaGymException(status, message)
) : MaeumGaGymException(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, INTERNAL_SERVER_ERROR.responseMessage)
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.info.maeumgagym.infrastructure.error.filter

import com.info.maeumgagym.common.exception.*
import com.info.maeumgagym.infrastructure.error.vo.ErrorLog
import com.info.maeumgagym.infrastructure.error.logger.ErrorLogger
import com.info.maeumgagym.infrastructure.error.vo.ErrorInfo
import com.info.maeumgagym.infrastructure.response.writer.DefaultHttpServletResponseWriter
import com.info.maeumgagym.infrastructure.response.writer.ErrorLogHttpServletResponseWriter
import org.springframework.web.filter.OncePerRequestFilter
Expand All @@ -14,12 +15,14 @@ import javax.servlet.http.HttpServletResponse
* [Exception]이 발생했을 때, 응답 및 로그를 작성
*
* [doFilter], 정확히는 [doFilterInternal]를 *try*문으로 감싸 실행.
* 이후 발생한 모든 예외를 *catch*해 각 예외에 따라 [ErrorLog]및 그에 대한 Response를 작성.
* 이후 발생한 모든 예외를 *catch*해 예외를 [ErrorInfo]로 가공하고, 로그 및 응답을 작성.
* [ErrorLogger]에서 로그를 작성하고, [ErrorLogHttpServletResponseWriter]에서 응답을 작성함.
*
* 원래 [DispatcherServlet][org.springframework.web.servlet.DispatcherServlet] 통과 이후 발생한 예외는 [NestedServletException.cause]로 감싸져 *throw*되지만, [ExceptionConvertFilter]에서 이를 [MaeumGaGymException]의 하위 타입으로 변환함. 자세한 것은 [ExceptionConvertFilter] 참조.
*
* 해당 *Filter*의 순서 설정 정보는 [ApplicationFilterChainConfig][com.info.maeumgagym.infrastructure.filter.config.ApplicationFilterChainConfig]에 존재
*
* @see ErrorLogger
* @see ExceptionConvertFilter
* @see ErrorLogHttpServletResponseWriter
*
Expand All @@ -28,7 +31,8 @@ import javax.servlet.http.HttpServletResponse
*/
class ErrorLogResponseFilter(
private val defaultHttpServletResponseWriter: DefaultHttpServletResponseWriter,
private val errorLogHttpServletResponseWriter: ErrorLogHttpServletResponseWriter
private val errorLogHttpServletResponseWriter: ErrorLogHttpServletResponseWriter,
private val errorLogger: ErrorLogger
) : OncePerRequestFilter() {

override fun doFilterInternal(
Expand All @@ -39,47 +43,37 @@ class ErrorLogResponseFilter(
try {
filterChain.doFilter(request, response)
} catch (e: MaeumGaGymException) {
resolveMaeumGaGymException(e, response)
resolveException(e, response)
} catch (e: Exception) {
resolveUnknownException(e, response)
resolveException(
CriticalException("Unknown Type Exception Came to ${javaClass.name}").apply { initCause(e) },
response
)
}
}

private fun resolveMaeumGaGymException(e: MaeumGaGymException, response: HttpServletResponse) {
if (isOkStatus(e.status)) {
private fun resolveException(e: MaeumGaGymException, response: HttpServletResponse) {
if (isSuccessStatusCode(e.status)) {
defaultHttpServletResponseWriter.doDefaultSettingWithStatusCode(response, e.status)
return
}

val errorLog = printErrorLogAndReturn(e)
val errorInfo = ErrorInfo.of(e)

if (isUnknownMaeumGaGymException(e)) {
e.printStackTrace()
}

errorLogHttpServletResponseWriter.writeResponseWithErrorLog(response, errorLog)
}

private fun resolveUnknownException(e: Exception, response: HttpServletResponse) {
val errorLog = printErrorLogAndReturn(e)

e.printStackTrace()
errorLogHttpServletResponseWriter.writeErrorResponse(response, errorInfo)

errorLogHttpServletResponseWriter.writeResponseWithErrorLog(response, errorLog)
errorLogger.printLog(errorInfo)
}

private fun isOkStatus(status: Int): Boolean =
private fun isSuccessStatusCode(status: Int): Boolean =
status in 200..299

private fun isUnknownMaeumGaGymException(e: MaeumGaGymException): Boolean =
e !is BusinessLogicException && e !is SecurityException &&
e !is FilterException && e !is InterceptorException &&
e !is AuthenticationException && e !is PresentationValidationException

private fun printErrorLogAndReturn(e: Exception): ErrorLog {
ErrorLog.of(e).run {
logger.info(this.toString())
return this
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.info.maeumgagym.infrastructure.error.filter

import com.info.maeumgagym.common.exception.MaeumGaGymException
import com.info.maeumgagym.common.exception.PresentationValidationException
import com.info.maeumgagym.infrastructure.error.repository.ExceptionRepository
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.MissingServletRequestParameterException
import org.springframework.web.filter.GenericFilterBean
Expand All @@ -25,7 +26,7 @@ import javax.validation.ConstraintViolationException
* ```
* - [MaeumGaGymException] 및 그 하위 타입일 경우 [MaeumGaGymException]으로 변환.
* - Presentation 계층에서 Validation이 실패했을 경우 발생하는 예외 중 하나일 경우 [PresentationValidationException]으로 변환; 변환되는 타입 : [MethodArgumentNotValidException], [ConstraintViolationException], [MissingServletRequestParameterException]
* - 그 외에는 그대로 변환
* - 그 외에는 일반 [MaeumGaGymException]으로 감싸짐
*
* 해당 *Filter*의 순서 설정 정보는 [ApplicationFilterChainConfig][com.info.maeumgagym.infrastructure.filter.config.ApplicationFilterChainConfig]에 존재
*
Expand All @@ -35,13 +36,15 @@ import javax.validation.ConstraintViolationException
* @since 22.02.2024
*/
class ExceptionConvertFilter(
private val exceptionRepository: com.info.maeumgagym.infrastructure.error.repository.ExceptionRepository
private val exceptionRepository: ExceptionRepository
) : GenericFilterBean() {

override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
try {
chain.doFilter(request, response)
exceptionRepository.throwIt()
} catch (e: MaeumGaGymException) {
throw e
} catch (e: NestedServletException) {
throw when (e.cause) {
is MaeumGaGymException -> e.cause as MaeumGaGymException
Expand All @@ -52,7 +55,7 @@ class ExceptionConvertFilter(
400,
"field : ${fieldError?.field}\nparameter : $parameter",
mapOf()
)
).apply { initCause(e) }
}

is ConstraintViolationException ->
Expand All @@ -63,7 +66,7 @@ class ExceptionConvertFilter(
fields = constraintViolations.associate {
Pair<String, String>(it.propertyPath.toString(), it.message)
}
)
).apply { initCause(e) }
}

is MissingServletRequestParameterException ->
Expand All @@ -72,7 +75,7 @@ class ExceptionConvertFilter(
status = 400,
message = message,
fields = mapOf(Pair(parameterName, localizedMessage))
)
).apply { initCause(e) }
}

else -> e.cause ?: e
Expand All @@ -82,7 +85,13 @@ class ExceptionConvertFilter(
status = 400,
message = "DateTime Format Wrong",
fields = mapOf()
)
).apply { initCause(e) }
} catch (e: Exception) {
throw MaeumGaGymException(
500,
e.message ?: e.localizedMessage ?: ("Cause Exception Class : " + e.javaClass.name),
MaeumGaGymException.INTERNAL_SERVER_ERROR.responseMessage
).apply { initCause(e) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.info.maeumgagym.infrastructure.error.logger

import com.info.maeumgagym.infrastructure.error.vo.ErrorInfo

/**
* [ErrorInfo]를 로그로 출력
*
* @author Daybreak312
* @since 09-05-2024
*/
interface ErrorLogger {

fun printLog(errorInfo: ErrorInfo)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.info.maeumgagym.infrastructure.error.logger

import com.info.maeumgagym.infrastructure.error.vo.ErrorInfo
import org.apache.commons.logging.LogFactory
import org.springframework.stereotype.Component

@Component
class ErrorLoggerImpl : ErrorLogger {

private val logger = LogFactory.getLog(javaClass)

override fun printLog(errorInfo: ErrorInfo) {
errorInfo.run {
logger.warn(
"[$id] $status : \"$message\" with exception \"$exceptionClassName\""
)

logger.warn(
"\tat ${errorOccurredClassName.first()}"
)

errorOccurredClassName.subList(1, errorOccurredClassName.size).forEach {
logger.warn("\t or $it")
}
}
}
}
Loading
Loading