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

[FEAT] Global Handler 및 Internal Server Error 슬랙 알림 추가 #188

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ dependencies {

implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'

// slack 알림
implementation 'com.slack.api:slack-api-client:1.27.2'

// slack DTO json Formatting
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}

tasks.named('test') {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/gam/api/common/ApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ public static ApiResponse fail(String message){
.build();
}

public static ApiResponse serverError(Object object){
return ApiResponse.builder()
.data(object)
.build();
}
}
53 changes: 51 additions & 2 deletions src/main/java/com/gam/api/common/ErrorHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
import com.gam.api.common.exception.ReportException;
import com.gam.api.common.exception.ScrapException;
import com.gam.api.common.exception.WorkException;
import com.gam.api.common.message.ExceptionMessage;
import com.gam.api.config.SlackConfig;
import com.slack.api.Slack;
import java.time.LocalDateTime;
import javax.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
Expand All @@ -15,11 +22,34 @@

import javax.persistence.EntityNotFoundException;

import static com.gam.api.common.message.ExceptionMessage.EMPTY_METHOD_ARGUMENT;

@RestControllerAdvice
@RequiredArgsConstructor
public class ErrorHandler {

private final Slack slack;
private final SlackConfig slackConfig;

/** Internal Server Error + Slack Alert **/
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleException(Exception exception, HttpServletRequest request) {
val errorDTO = requestToDTO(exception, request);
sendSlackAlarm(errorDTO.toString());

ApiResponse response = ApiResponse.serverError(errorDTO);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleRuntimeException(RuntimeException exception, HttpServletRequest request) {
val errorDTO = requestToDTO(exception, request);
sendSlackAlarm(errorDTO.toString());

ApiResponse response = ApiResponse.serverError(errorDTO);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}


/** Custom Error + 4__ Error Handler **/
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ApiResponse> handleEntityNotFoundException(EntityNotFoundException exception) {
ApiResponse response = ApiResponse.fail(exception.getMessage());
Expand Down Expand Up @@ -73,4 +103,23 @@ public ResponseEntity<ApiResponse> ReportException(ReportException exception){
ApiResponse response = ApiResponse.fail(exception.getMessage());
return new ResponseEntity<>(response, exception.getStatusCode());
}

private InternalServerErrorDTO requestToDTO(Exception exception, HttpServletRequest request) {
val header = InternalServerErrorDTO.extractHeaders(request);
return InternalServerErrorDTO.of(header, request.getMethod(), request.getRequestURL().toString(),
exception.getMessage(), exception.getClass().getName(), LocalDateTime.now());
}

private void sendSlackAlarm(String dto) {
try{
val slackResponse = slack.send(slackConfig.getUrl(), SlackErrorPayload.of(dto)).getBody();

if(!slackResponse.equals("ok")) {
throw new Exception(ExceptionMessage.NOT_POST_SLACK_ALARM.getMessage());// todo log -> 슬랙알림 안감
}
}catch (Exception exception) {
System.out.println(exception.toString()); // todo log
}
}

}
82 changes: 82 additions & 0 deletions src/main/java/com/gam/api/common/InternalServerErrorDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.gam.api.common;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class InternalServerErrorDTO {
private String header = null;
private String httpMethod = null;
private String URL = null ;
private String message = null;

private String errorClass = null;

private String dateTime = "";

@Builder
private InternalServerErrorDTO(String header, String httpMethod, String URL, String message,
String errorClass, String dateTime) {
this.header = header;
this.httpMethod = httpMethod;
this.URL = URL;
this.message = message;
this.errorClass = errorClass;
this.dateTime = dateTime;
}

public static InternalServerErrorDTO of(String header, String httpMethod, String URL, String message,
String errorClass, LocalDateTime nowDateTime) {
if(Objects.isNull(nowDateTime)) {
nowDateTime = LocalDateTime.now();
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String now = nowDateTime.format(formatter);

return InternalServerErrorDTO.builder()
.header(header)
.httpMethod(httpMethod)
.URL(URL)
.message(message)
.errorClass(errorClass)
.dateTime(now)
.build();
}

public static String extractHeaders(HttpServletRequest request) {
StringBuilder headers = new StringBuilder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
headers.append(headerName).append(": ").append(headerValue);
}
return headers.toString();
}

@Override
public String toString() {
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
String jsonString = null;
try {
jsonString = writer.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException(e); //todo Log
}
return jsonString;
}

}
35 changes: 35 additions & 0 deletions src/main/java/com/gam/api/common/SlackErrorPayload.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.gam.api.common;

import static com.slack.api.model.block.Blocks.section;
import static com.slack.api.model.block.composition.BlockCompositions.markdownText;
import static java.util.Arrays.asList;

import com.slack.api.webhook.Payload;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class SlackErrorPayload {

private Payload payload;

@Builder
private SlackErrorPayload(Payload payload) {
this.payload = payload;
}

public static Payload of(String errorDto) {
// Create Payload with a code block
String codeBlock = "```\n" +errorDto + "```";

return Payload.builder()
.blocks(asList(
section(s -> s.text(markdownText(mt -> mt.text(codeBlock))))
))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ public enum ExceptionMessage {

/** Report **/
ALREADY_REPORTED_USER("이미 신고 한 유저입니다. 신고 처리 진행중"),
NOT_MATCH_DB_BLOCK_STATUS("DB의 userScrap 상태와, 보낸 userScrap 상태가 같지 않습니다.");
NOT_MATCH_DB_BLOCK_STATUS("DB의 userScrap 상태와, 보낸 userScrap 상태가 같지 않습니다."),

/** slack **/
NOT_POST_SLACK_ALARM("슬랙 알림이 전송되지 않았습니다.");

private final String message;
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/gam/api/config/SlackConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.gam.api.config;

import com.slack.api.Slack;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Getter
@Configuration
public class SlackConfig {

@Value("${slack.url}")
private String url;

@Bean
public Slack slack() {
return Slack.getInstance();
}
}
Loading