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

Feature | #79 | @lcomment | slack webhook 구성 및 ExceptionHandler에 추가 #90

Merged
merged 5 commits into from
Mar 1, 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
1 change: 1 addition & 0 deletions .github/workflows/cd-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions lovebird-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<ApiResponse<Unit>> {
Expand All @@ -40,14 +45,14 @@ class GlobalControllerAdvice {
}

@ExceptionHandler(HttpRequestMethodNotSupportedException::class)
fun handleMethodNotSupportedException(e: HttpRequestMethodNotSupportedException): ResponseEntity<ApiResponse<Unit>> {
fun handleMethodNotSupportedException(e: HttpRequestMethodNotSupportedException, request: HttpServletRequest): ResponseEntity<ApiResponse<Unit>> {
return ResponseEntity
.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(ApiResponse.fail(ReturnCode.METHOD_NOT_ALLOWED))
}

@ExceptionHandler(MethodArgumentNotValidException::class)
fun badRequestExHandler(bindingResult: BindingResult): ResponseEntity<ApiResponse<*>> {
fun badRequestExHandler(bindingResult: BindingResult): ResponseEntity<ApiResponse<List<FieldError>>> {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(
Expand All @@ -61,7 +66,9 @@ class GlobalControllerAdvice {
@ExceptionHandler(
value = [SQLException::class]
)
fun handleServerException(e: SQLException): ResponseEntity<ApiResponse<Unit>> {
fun handleServerException(e: SQLException, request: HttpServletRequest): ResponseEntity<ApiResponse<Unit>> {
slackService.sendSlackForError(e, request)

Comment on lines +69 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error 전체를 응답하는 거 말고 추출해서 간략화하는 방법은은 없을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유의미한 error stack flow를 뽑는 것에 대한 내용은 없어서요.. 이후에 찾아봐야 할 것 같아요

return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(ReturnCode.INTERNAL_SERVER_ERROR))
Expand All @@ -70,7 +77,9 @@ class GlobalControllerAdvice {
@ExceptionHandler(
value = [RuntimeException::class]
)
fun handleBusinessException(e: RuntimeException): ResponseEntity<ApiResponse<Unit>> {
fun handleBusinessException(e: RuntimeException, request: HttpServletRequest): ResponseEntity<ApiResponse<Unit>> {
slackService.sendSlackForError(e, request)

return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(ReturnCode.INTERNAL_SERVER_ERROR))
Expand Down
Original file line number Diff line number Diff line change
@@ -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.default")
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()
}
}
4 changes: 4 additions & 0 deletions lovebird-api/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ jwt:
refresh-token: 2592000000
grant-type: Bearer

slack:
webhook:
is-enable: true
url: ${SLACK_WEBHOOK_URL}
4 changes: 4 additions & 0 deletions lovebird-api/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ jwt:
refresh-token: 2592000000
grant-type: Bearer

slack:
webhook:
is-enable: false
url: url
5 changes: 5 additions & 0 deletions lovebird-api/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
2 changes: 1 addition & 1 deletion lovebird-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand Down
Loading