From f199480f0347630ab10dafce38439b6c85ecf828 Mon Sep 17 00:00:00 2001 From: Hyunseok Ko Date: Thu, 29 Feb 2024 17:28:47 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20slack=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lovebird-api/build.gradle.kts | 3 +++ lovebird-api/src/main/resources/application-dev.yml | 4 ++++ lovebird-api/src/main/resources/application-local.yml | 4 ++++ lovebird-api/src/main/resources/application-prod.yml | 5 +++++ lovebird-api/src/main/resources/application.yml | 2 +- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lovebird-api/build.gradle.kts b/lovebird-api/build.gradle.kts index 965443d..41ce0b5 100644 --- a/lovebird-api/build.gradle.kts +++ b/lovebird-api/build.gradle.kts @@ -33,6 +33,9 @@ dependencies { implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5") + // Slack + implementation("net.gpedro.integrations.slack:slack-webhook:1.4.0") + // Test testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") diff --git a/lovebird-api/src/main/resources/application-dev.yml b/lovebird-api/src/main/resources/application-dev.yml index 2066860..bdde4aa 100644 --- a/lovebird-api/src/main/resources/application-dev.yml +++ b/lovebird-api/src/main/resources/application-dev.yml @@ -12,3 +12,7 @@ jwt: refresh-token: 2592000000 grant-type: Bearer +slack: + webhook: + is-enable: true + url: ${SLACK_WEBHOOK_URL} diff --git a/lovebird-api/src/main/resources/application-local.yml b/lovebird-api/src/main/resources/application-local.yml index 84833ac..95db2f1 100644 --- a/lovebird-api/src/main/resources/application-local.yml +++ b/lovebird-api/src/main/resources/application-local.yml @@ -12,3 +12,7 @@ jwt: refresh-token: 2592000000 grant-type: Bearer +slack: + webhook: + is-enable: false + url: url diff --git a/lovebird-api/src/main/resources/application-prod.yml b/lovebird-api/src/main/resources/application-prod.yml index fb0ca35..1063303 100644 --- a/lovebird-api/src/main/resources/application-prod.yml +++ b/lovebird-api/src/main/resources/application-prod.yml @@ -11,3 +11,8 @@ jwt: access-token: ${JWT_ACCESS_TOKEN_EXPIRATION} refresh-token: ${JWT_REFRESH_TOKEN_EXPIRATION} grant-type: Bearer + +slack: + webhook: + is-enable: true + url: ${SLACK_WEBHOOK_URL} diff --git a/lovebird-api/src/main/resources/application.yml b/lovebird-api/src/main/resources/application.yml index 8944cc3..fb738e3 100644 --- a/lovebird-api/src/main/resources/application.yml +++ b/lovebird-api/src/main/resources/application.yml @@ -3,7 +3,7 @@ spring: default: ${APPLICATION_PROFILE} application: title: Lovebird - version: 1.0.2 + version: 1.1.0 banner: location: classpath:/app-banner.dat config: From 53ddc3b25b9d70293a1d9fa4119cc5b4a84f0b49 Mon Sep 17 00:00:00 2001 From: Hyunseok Ko Date: Thu, 29 Feb 2024 17:29:37 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feature:=20slack=20webhook=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20exception=20handler=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lovebird/api/config/SlackWebhookConfig.kt | 18 ++++++ .../api/controller/GlobalControllerAdvice.kt | 19 ++++-- .../api/service/slack/SlackService.kt | 61 +++++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 lovebird-api/src/main/kotlin/com/lovebird/api/config/SlackWebhookConfig.kt create mode 100644 lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt diff --git a/lovebird-api/src/main/kotlin/com/lovebird/api/config/SlackWebhookConfig.kt b/lovebird-api/src/main/kotlin/com/lovebird/api/config/SlackWebhookConfig.kt new file mode 100644 index 0000000..55383aa --- /dev/null +++ b/lovebird-api/src/main/kotlin/com/lovebird/api/config/SlackWebhookConfig.kt @@ -0,0 +1,18 @@ +package com.lovebird.api.config + +import net.gpedro.integrations.slack.SlackApi +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class SlackWebhookConfig( + @Value("\${slack.webhook.url}") + private val slackWebhookUrl: String +) { + + @Bean + fun slackApi(): SlackApi { + return SlackApi(slackWebhookUrl) + } +} diff --git a/lovebird-api/src/main/kotlin/com/lovebird/api/controller/GlobalControllerAdvice.kt b/lovebird-api/src/main/kotlin/com/lovebird/api/controller/GlobalControllerAdvice.kt index 76abdb9..38a90cc 100644 --- a/lovebird-api/src/main/kotlin/com/lovebird/api/controller/GlobalControllerAdvice.kt +++ b/lovebird-api/src/main/kotlin/com/lovebird/api/controller/GlobalControllerAdvice.kt @@ -1,12 +1,15 @@ package com.lovebird.api.controller +import com.lovebird.api.service.slack.SlackService import com.lovebird.common.enums.ReturnCode import com.lovebird.common.exception.LbException import com.lovebird.common.response.ApiResponse +import jakarta.servlet.http.HttpServletRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.http.converter.HttpMessageNotReadableException import org.springframework.validation.BindingResult +import org.springframework.validation.FieldError import org.springframework.web.HttpRequestMethodNotSupportedException import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.MissingServletRequestParameterException @@ -17,7 +20,9 @@ import java.sql.SQLException import kotlin.RuntimeException @RestControllerAdvice -class GlobalControllerAdvice { +class GlobalControllerAdvice( + private val slackService: SlackService +) { @ExceptionHandler(LbException::class) fun handleLbException(e: LbException): ResponseEntity> { @@ -40,14 +45,14 @@ class GlobalControllerAdvice { } @ExceptionHandler(HttpRequestMethodNotSupportedException::class) - fun handleMethodNotSupportedException(e: HttpRequestMethodNotSupportedException): ResponseEntity> { + fun handleMethodNotSupportedException(e: HttpRequestMethodNotSupportedException, request: HttpServletRequest): ResponseEntity> { return ResponseEntity .status(HttpStatus.METHOD_NOT_ALLOWED) .body(ApiResponse.fail(ReturnCode.METHOD_NOT_ALLOWED)) } @ExceptionHandler(MethodArgumentNotValidException::class) - fun badRequestExHandler(bindingResult: BindingResult): ResponseEntity> { + fun badRequestExHandler(bindingResult: BindingResult): ResponseEntity>> { return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body( @@ -61,7 +66,9 @@ class GlobalControllerAdvice { @ExceptionHandler( value = [SQLException::class] ) - fun handleServerException(e: SQLException): ResponseEntity> { + fun handleServerException(e: SQLException, request: HttpServletRequest): ResponseEntity> { + slackService.sendSlackForError(e, request) + return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(ReturnCode.INTERNAL_SERVER_ERROR)) @@ -70,7 +77,9 @@ class GlobalControllerAdvice { @ExceptionHandler( value = [RuntimeException::class] ) - fun handleBusinessException(e: RuntimeException): ResponseEntity> { + fun handleBusinessException(e: RuntimeException, request: HttpServletRequest): ResponseEntity> { + slackService.sendSlackForError(e, request) + return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(ReturnCode.INTERNAL_SERVER_ERROR)) diff --git a/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt b/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt new file mode 100644 index 0000000..8e15b36 --- /dev/null +++ b/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt @@ -0,0 +1,61 @@ +package com.lovebird.api.service.slack + +import jakarta.servlet.http.HttpServletRequest +import net.gpedro.integrations.slack.SlackApi +import net.gpedro.integrations.slack.SlackAttachment +import net.gpedro.integrations.slack.SlackField +import net.gpedro.integrations.slack.SlackMessage +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.env.Environment +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class SlackService( + private val slackApi: SlackApi, + private val environment: Environment, + @Value("\${slack.webhook.is-enable}") + private val isEnable: Boolean +) { + + fun sendSlackForError(exception: Exception, request: HttpServletRequest) { + if (!isEnable) { + return + } + val slackAttachment = SlackAttachment() + slackAttachment.setFallback("Error") + slackAttachment.setColor("danger") + slackAttachment.setTitle("Error Detect") + slackAttachment.setTitleLink(request.contextPath) + slackAttachment.setText(exception.stackTraceToString()) + slackAttachment.setFields( + listOf( + SlackField().setTitle("Request URL").setValue(request.requestURL.toString()), + SlackField().setTitle("Request Method").setValue(request.method), + SlackField().setTitle("Request Parameter").setValue(getRequestParameters(request)), + SlackField().setTitle("Request Time").setValue(LocalDateTime.now().toString()), + SlackField().setTitle("Request IP").setValue(request.remoteAddr), + SlackField().setTitle("Request User-Agent").setValue(request.getHeader("User-Agent")) + ) + ) + val profile = environment.getProperty("spring.profiles.active") + val slackMessage = SlackMessage() + + slackMessage.setAttachments(listOf(slackAttachment)) + slackMessage.setChannel("#error-log") + slackMessage.setUsername("$profile API Error") + slackMessage.setIcon(":alert:") + slackMessage.setText("$profile api 에러 발생") + + slackApi.call(slackMessage) + } + + private fun getRequestParameters(request: HttpServletRequest): String { + val parameterMap = request.parameterMap + val sb = StringBuilder() + parameterMap.forEach { (key, value) -> + sb.append("$key: ${value.joinToString(", ")}\n") + } + return sb.toString() + } +} From 6e99e1fb75bac25440aaa2bc33192b4f77d72bf3 Mon Sep 17 00:00:00 2001 From: Hyunseok Ko Date: Thu, 29 Feb 2024 17:29:52 +0900 Subject: [PATCH 3/5] =?UTF-8?q?chore:=20cd=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-develop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd-develop.yml b/.github/workflows/cd-develop.yml index 1b0880e..be6c140 100644 --- a/.github/workflows/cd-develop.yml +++ b/.github/workflows/cd-develop.yml @@ -70,6 +70,7 @@ jobs: jwt.access-header: ${{ secrets.ACCESS_HEADER }} jwt.refresh-header: ${{ secrets.REFRESH_HEADER }} jwt.secret: ${{ secrets.DEV_JWT_SECRET }} + slack.webhook.url: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Set client-dev yml file uses: microsoft/variable-substitution@v1 From dcee0a7bfdfc2a0b086fe61c5b2e2ec33da7b26a Mon Sep 17 00:00:00 2001 From: Hyunseok Ko Date: Thu, 29 Feb 2024 17:35:52 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/com/lovebird/api/service/slack/SlackService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt b/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt index 8e15b36..be8cbbc 100644 --- a/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt +++ b/lovebird-api/src/main/kotlin/com/lovebird/api/service/slack/SlackService.kt @@ -38,7 +38,7 @@ class SlackService( SlackField().setTitle("Request User-Agent").setValue(request.getHeader("User-Agent")) ) ) - val profile = environment.getProperty("spring.profiles.active") + val profile = environment.getProperty("spring.profiles.default") val slackMessage = SlackMessage() slackMessage.setAttachments(listOf(slackAttachment)) From f9241028bf526e17756a15a84f9bc8d77df99a3d Mon Sep 17 00:00:00 2001 From: Hyunseok Ko Date: Fri, 1 Mar 2024 11:51:22 +0900 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=20mock=20bean=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/lovebird/api/common/base/ControllerDescribeSpec.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lovebird-api/src/test/kotlin/com/lovebird/api/common/base/ControllerDescribeSpec.kt b/lovebird-api/src/test/kotlin/com/lovebird/api/common/base/ControllerDescribeSpec.kt index 9bfe241..4add021 100644 --- a/lovebird-api/src/test/kotlin/com/lovebird/api/common/base/ControllerDescribeSpec.kt +++ b/lovebird-api/src/test/kotlin/com/lovebird/api/common/base/ControllerDescribeSpec.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.lovebird.api.config.WebMvcConfig +import com.lovebird.api.service.slack.SlackService import com.lovebird.api.utils.restdocs.OBJECT import com.lovebird.api.utils.restdocs.RestDocsField import com.lovebird.api.utils.restdocs.STRING @@ -27,6 +28,9 @@ abstract class ControllerDescribeSpec( @MockkBean protected lateinit var jwtValidator: JwtValidator + @MockkBean + protected lateinit var slackService: SlackService + companion object { private val mapper: ObjectMapper = ObjectMapper() .registerModule(JavaTimeModule())