Skip to content

Commit

Permalink
[KAN-116] 에러 발생시 디스코드 에러채널에 request,response 전송한다 (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
sinkyoungdeok authored May 29, 2024
1 parent 18df209 commit cf57688
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 24 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ dependencies {
// AWS SES
implementation("software.amazon.awssdk:ses:2.20.114")

// Discord
implementation("club.minnced:discord-webhooks:0.8.4")

// Kotlin
val coroutineVersion = "1.6.3"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.restaurant.be.common.config

import club.minnced.discord.webhook.WebhookClient
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class DiscordClientConfig(
@Value("\${discord.monitor}") val monitorUrl: String
) {
private val dummyUrl =
"https://discord.com/api/webhooks/99999990000000/213aWRR5sEeY5UhOk7twvFSDFVC-Feqw"

@Bean("MonitorWebhook")
fun monitorReportClient(): WebhookClient {
return if (monitorUrl.startsWith("https://discord.com")) {
WebhookClient.withUrl(monitorUrl)
} else {
WebhookClient.withUrl(dummyUrl)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.restaurant.be.common.exception

import club.minnced.discord.webhook.WebhookClient
import club.minnced.discord.webhook.send.WebhookEmbed
import club.minnced.discord.webhook.send.WebhookMessageBuilder
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.restaurant.be.common.response.CommonResponse
import com.restaurant.be.common.response.Error
import com.restaurant.be.common.response.ErrorCode
import kotlinx.coroutines.runBlocking
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.FieldError
Expand All @@ -14,33 +19,74 @@ import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.bind.support.WebExchangeBindException
import org.springframework.web.server.ServerWebInputException
import java.io.BufferedReader
import java.security.SignatureException
import javax.servlet.http.HttpServletRequest

@RestControllerAdvice
class GlobalExceptionHandler {
class GlobalExceptionHandler(
@Qualifier("MonitorWebhook") private val webhookClient: WebhookClient
) {

@ExceptionHandler(value = [ServerException::class])
fun handleServerException(
ex: ServerException
ex: ServerException,
request: HttpServletRequest
): ResponseEntity<Any> {
val embed = WebhookEmbed(
null,
null,
"code: " + ex.code + "\n" + ex.javaClass.simpleName,
null,
null,
null,
WebhookEmbed.EmbedTitle(request.requestURI, null),
null,
emptyList()
)
val builder = WebhookMessageBuilder()
builder.setContent(ex.message)
builder.addEmbeds(embed)
builder.addEmbeds(parseBodyToEmbed(request))
builder.addEmbeds(parseUserAgentToEmbed(request))
webhookClient.send(builder.build())

val response = CommonResponse.fail(ex.message, ex.javaClass.simpleName)
return ResponseEntity(response, null, ex.code)
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = [ServerWebInputException::class])
fun methodArgumentNotValidException(
e: ServerWebInputException
e: ServerWebInputException,
request: HttpServletRequest
): CommonResponse<String?> {
val data =
if (e.cause?.cause is MissingKotlinParameterException) {
val param = (e.cause?.cause as MissingKotlinParameterException).parameter.name
"항목 ${param}을 확인해주세요"
} else if (e.cause?.cause is MismatchedInputException) {
e.message
} else {
null
}
val data = if (e.cause?.cause is MissingKotlinParameterException) {
val param = (e.cause?.cause as MissingKotlinParameterException).parameter.name
"항목 ${param}을 확인해주세요"
} else if (e.cause?.cause is MismatchedInputException) {
e.message
} else {
null
}

val embed = WebhookEmbed(
null,
null,
data,
null,
null,
null,
WebhookEmbed.EmbedTitle(request.requestURI, null),
null,
emptyList()
)
val builder = WebhookMessageBuilder()
builder.setContent(e.message)
builder.addEmbeds(embed)
builder.addEmbeds(parseBodyToEmbed(request))
builder.addEmbeds(parseUserAgentToEmbed(request))
webhookClient.send(builder.build())

val errorResponse = CommonResponse.fail(data, ErrorCode.COMMON_NULL_PARAMETER)
return errorResponse
Expand All @@ -49,31 +95,67 @@ class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = [WebExchangeBindException::class, MethodArgumentNotValidException::class])
fun methodArgumentNotValidException(
e: WebExchangeBindException
e: WebExchangeBindException,
request: HttpServletRequest
): CommonResponse<String> {
val errors = mutableListOf<Error>()
e.allErrors.forEach {
val error =
Error(
field = (it as FieldError).field,
message = it.defaultMessage,
value = it.rejectedValue
)
val error = Error(
field = (it as FieldError).field,
message = it.defaultMessage,
value = it.rejectedValue
)
errors.add(error)
}

val embed = WebhookEmbed(
null,
null,
errors.toString(),
null,
null,
null,
WebhookEmbed.EmbedTitle(request.requestURI, null),
null,
emptyList()
)
val builder = WebhookMessageBuilder()
builder.setContent(ErrorCode.COMMON_INVALID_PARAMETER.errorMsg)
builder.addEmbeds(embed)
builder.addEmbeds(parseBodyToEmbed(request))
builder.addEmbeds(parseUserAgentToEmbed(request))
webhookClient.send(builder.build())

val errorResponse =
CommonResponse
.fail(errors.toString(), ErrorCode.COMMON_INVALID_PARAMETER)
CommonResponse.fail(errors.toString(), ErrorCode.COMMON_INVALID_PARAMETER)

return errorResponse
}

@ExceptionHandler(SignatureException::class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
fun handleSignatureException(
e: SignatureException
e: SignatureException,
request: HttpServletRequest
): CommonResponse<String?> {
val embed = WebhookEmbed(
null,
null,
e.message + "/" + e::class.java.simpleName,
null,
null,
null,
WebhookEmbed.EmbedTitle(request.requestURI, null),
null,
emptyList()
)
val builder = WebhookMessageBuilder()
builder.setContent("서버에서 정의하지 않은 서버입니다.")
builder.addEmbeds(embed)
builder.addEmbeds(parseBodyToEmbed(request))
builder.addEmbeds(parseUserAgentToEmbed(request))
webhookClient.send(builder.build())

val errorResponse = CommonResponse.fail(e.message, e::class.java.simpleName)
return errorResponse
}
Expand All @@ -86,4 +168,42 @@ class GlobalExceptionHandler {
val errorResponse = CommonResponse.fail(e.message, e::class.java.simpleName)
return errorResponse
}

private fun parseBodyToEmbed(request: HttpServletRequest): WebhookEmbed {
return WebhookEmbed(
null,
null,
parseBody(request),
null,
null,
null,
WebhookEmbed.EmbedTitle("Body", null),
null,
emptyList()
)
}

private fun parseBody(request: HttpServletRequest): String {
return runBlocking {
val body = runCatching {
request.inputStream.bufferedReader().use(BufferedReader::readText)
}.getOrDefault("")
body
}
}

private fun parseUserAgentToEmbed(request: HttpServletRequest): WebhookEmbed {
val userAgent = request.getHeader("User-Agent") ?: "Unknown"
return WebhookEmbed(
null,
null,
userAgent,
null,
null,
null,
WebhookEmbed.EmbedTitle("User Agent", null),
null,
emptyList()
)
}
}
5 changes: 4 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ aws:

es:
host: localhost
port: 9200
port: 9200

discord:
monitor: ${DISCORD_MONITOR:https:test}
5 changes: 4 additions & 1 deletion src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ aws:

es:
host: localhost
port: 9201
port: 9201

discord:
monitor: ${DISCORD_MONITOR:https:test}

0 comments on commit cf57688

Please sign in to comment.