Skip to content

Commit

Permalink
[feat] slack 에러 알림 내용 추가 (#167)
Browse files Browse the repository at this point in the history
* [feat] request multi read filter 추가 #165

* [feat] slack message 내용 추가 #165

* [feat] health check api 생성

* [chore] spotless 적용 #165
  • Loading branch information
wjdtkdgns authored Sep 18, 2023
1 parent e6fa106 commit eb02a07
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 12 deletions.
1 change: 0 additions & 1 deletion Api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'

// swagger
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package allchive.server.api.chore.controller;


import allchive.server.api.archiving.model.dto.request.UpdateArchivingRequest;
import allchive.server.domain.domains.archiving.domain.enums.Category;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/chore")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "etc. [chore]")
public class ChoreController {
@Operation(hidden = true)
@PostMapping(value = "error/{test}")
public void errorExample(
@RequestBody UpdateArchivingRequest updateArchivingRequest,
@RequestParam("category") Category category,
@PathVariable("test") Long test) {
throw new RuntimeException();
}

@Operation(hidden = true)
@GetMapping(value = "health")
public void errorExample() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package allchive.server.api.common.filter;


import com.amazonaws.util.IOUtils;
import java.io.*;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MultiReadInputStream extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;

public MultiReadInputStream(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) cacheInputStream();
return new CachedServletInputStream(cachedBytes.toByteArray());
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

private void cacheInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}

private static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream buffer;

public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}

@Override
public int read() {
return buffer.read();
}

@Override
public boolean isFinished() {
return buffer.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package allchive.server.api.common.filter;


import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

@Component
public class MultiReadInputStreamFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
MultiReadInputStream multiReadRequest =
new MultiReadInputStream((HttpServletRequest) request);
chain.doFilter(multiReadRequest, response);
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void destroy() {
Filter.super.destroy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.ContentCachingRequestWrapper;

@Slf4j
@RestControllerAdvice
Expand Down Expand Up @@ -98,9 +99,11 @@ public ResponseEntity<ErrorResponse> handleBaseErrorException(
@ExceptionHandler(BaseDynamicException.class)
public ResponseEntity<ErrorResponse> BaseDynamicExceptionHandler(
BaseDynamicException e, HttpServletRequest request) {
final ContentCachingRequestWrapper cachedRequest =
new ContentCachingRequestWrapper(request);
ErrorResponse errorResponse =
ErrorResponse.from(ErrorReason.of(e.getStatus(), e.getCode(), e.getMessage()));
Event.raise(SlackErrorEvent.from(e));
Event.raise(SlackErrorEvent.of(e, cachedRequest));
return ResponseEntity.status(HttpStatus.valueOf(e.getStatus())).body(errorResponse);
}

Expand All @@ -109,10 +112,12 @@ public ResponseEntity<ErrorResponse> BaseDynamicExceptionHandler(
protected ResponseEntity<ErrorResponse> handleException(
Exception e, HttpServletRequest request) {
log.error("Exception", e);
final ContentCachingRequestWrapper cachedRequest =
new ContentCachingRequestWrapper(request);
final GlobalErrorCode globalErrorCode = GlobalErrorCode._INTERNAL_SERVER_ERROR;
final ErrorReason errorReason = globalErrorCode.getErrorReason();
final ErrorResponse errorResponse = ErrorResponse.from(errorReason);
Event.raise(SlackErrorEvent.from(e));
Event.raise(SlackErrorEvent.of(e, cachedRequest));
return ResponseEntity.status(INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
}

http.authorizeRequests()
.mvcMatchers("/chore/**")
.permitAll()
.antMatchers(SwaggerPatterns)
.permitAll()
.mvcMatchers("/auth/oauth/**")
Expand Down
1 change: 1 addition & 0 deletions Core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
api 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@

import lombok.Builder;
import lombok.Getter;
import org.springframework.web.util.ContentCachingRequestWrapper;

@Getter
public class SlackErrorEvent {
private Exception exception;
private ContentCachingRequestWrapper cachedRequest;

@Builder
private SlackErrorEvent(Exception exception) {
private SlackErrorEvent(Exception exception, ContentCachingRequestWrapper cachedRequest) {
this.exception = exception;
this.cachedRequest = cachedRequest;
}

public static SlackErrorEvent from(Exception exception) {
return SlackErrorEvent.builder().exception(exception).build();
public static SlackErrorEvent of(
Exception exception, ContentCachingRequestWrapper cachedRequest) {
return SlackErrorEvent.builder().exception(exception).cachedRequest(cachedRequest).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
import com.slack.api.model.block.composition.TextObject;
import com.slack.api.webhook.Payload;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletInputStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

@Component
@RequiredArgsConstructor
Expand All @@ -30,26 +34,32 @@ public class SlackMessageGenerater {

public Payload generateErrorMsg(SlackErrorEvent event) throws IOException {
final Exception e = event.getException();
final ContentCachingRequestWrapper cachedRequest = event.getCachedRequest();

List<LayoutBlock> layoutBlocks = new ArrayList<>();
// 제목
layoutBlocks.add(HeaderBlock.builder().text(plainText("에러 알림")).build());
// 구분선
layoutBlocks.add(new DividerBlock());
// timeØ
layoutBlocks.add(getTime());
// IP + Method, Addr
// time, Addr
layoutBlocks.add(makeSection(getTime(), getAddr(cachedRequest)));
// RequestBody + RequestParam
layoutBlocks.add(makeSection(getBody(cachedRequest), getParam(cachedRequest)));
layoutBlocks.add(new DividerBlock());
layoutBlocks.add(makeSection(getErrMessage(e), getErrStack(e)));

return Payload.builder().text("에러 알림").blocks(layoutBlocks).build();
}

private LayoutBlock getTime() {
private LayoutBlock getTimeBlock() {
MarkdownTextObject timeObj =
MarkdownTextObject.builder().text("* Time :*\n" + LocalDateTime.now()).build();
return Blocks.section(section -> section.fields(List.of(timeObj)));
}

private MarkdownTextObject getTime() {
return MarkdownTextObject.builder().text("* Time :*\n" + LocalDateTime.now()).build();
}

private LayoutBlock makeSection(TextObject first, TextObject second) {
return Blocks.section(section -> section.fields(List.of(first, second)));
}
Expand All @@ -66,6 +76,25 @@ private MarkdownTextObject getErrStack(Throwable throwable) {
return MarkdownTextObject.builder().text("* Stack Trace :*\n" + errorStack).build();
}

private MarkdownTextObject getAddr(ContentCachingRequestWrapper c) {
final String method = c.getMethod();
final String url = c.getRequestURL().toString();
return MarkdownTextObject.builder()
.text("* Request Addr :*\n" + method + " : " + url)
.build();
}

private MarkdownTextObject getBody(ContentCachingRequestWrapper c) throws IOException {
ServletInputStream inputStream = c.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
return MarkdownTextObject.builder().text("* Request Body :*\n" + messageBody).build();
}

private MarkdownTextObject getParam(ContentCachingRequestWrapper c) {
final String param = c.getQueryString();
return MarkdownTextObject.builder().text("* Request Param :*\n" + param).build();
}

public Payload generateAsyncErrorMsg(SlackAsyncErrorEvent event) {
String name = event.getName();
Throwable throwable = event.getThrowable();
Expand All @@ -88,7 +117,7 @@ public Payload generateAsyncErrorMsg(SlackAsyncErrorEvent event) {
section.fields(List.of(errorUserIdMarkdown, errorUserIpMarkdown))));

layoutBlocks.add(divider());
layoutBlocks.add(getTime());
layoutBlocks.add(getTimeBlock());
String errorStack = getErrorStack(throwable);
String message = throwable.toString();
MarkdownTextObject errorNameMarkdown =
Expand Down

0 comments on commit eb02a07

Please sign in to comment.