-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: 커스텀 slackAppender 뼈대 제작 * feat: ServletRequest 캐싱 로직 구현 * refactor: filter 전략 변경 * feat: Slack 알람 관련 interceptor 등록 * chore: webClient를 위한 webflux 의존성 추가. * feat: 슬랙으로 메세지 전송 기능 구현 * refactor: spring에서 제공해주는 wrapper로 변경 * test: 필요 없는 테스트 제거 * feat: AoP, ThreadLocal 기반으로 변경 * feat: Slack 알림 등록 * refactor: 기존 SlackAppender 제거 * refactor: 사용하지 않는 Appender 제거 * fix: SlackAlarm이 test에서도 전송되는 문제 해결 * refactor: SlackMessage 이름 변경 및 빈등록 * refactor: logger 위치 변경 * refactor: thread local 제거후 request scope 사용 * refactor: 잘못된 token에 대한 에러 핸들링 * refactor: 중복된 flayway 파일 제거 * refactor: objectmapper 주입받아 사용하도록 변경
- Loading branch information
Showing
13 changed files
with
325 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
backend/src/main/java/wooteco/prolog/common/exception/ExceptionController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
backend/src/main/java/wooteco/prolog/common/filter/ServletWrappingFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package wooteco.prolog.common.filter; | ||
|
||
import java.io.IOException; | ||
import javax.servlet.FilterChain; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
import org.springframework.web.util.ContentCachingRequestWrapper; | ||
import wooteco.prolog.common.slacklogger.RequestStorage; | ||
|
||
@Component | ||
public class ServletWrappingFilter extends OncePerRequestFilter { | ||
|
||
private final RequestStorage requestStorage; | ||
|
||
public ServletWrappingFilter(RequestStorage requestStorage) { | ||
this.requestStorage = requestStorage; | ||
} | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | ||
FilterChain filterChain) throws ServletException, IOException { | ||
|
||
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); | ||
requestStorage.set(wrappedRequest); | ||
|
||
filterChain.doFilter(wrappedRequest, response); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
backend/src/main/java/wooteco/prolog/common/slacklogger/ExceptionAppender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
import org.aspectj.lang.JoinPoint; | ||
import org.aspectj.lang.annotation.Aspect; | ||
import org.aspectj.lang.annotation.Before; | ||
import org.aspectj.lang.reflect.MethodSignature; | ||
import org.springframework.boot.autoconfigure.AutoConfigurationPackage; | ||
import org.springframework.context.annotation.Profile; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Slf4j | ||
@Aspect | ||
@Component | ||
@Profile({"prod", "dev"}) | ||
@AutoConfigurationPackage | ||
public class ExceptionAppender { | ||
|
||
private static final String SLACK_ALARM_FORMAT = "[SlackAlarm] %s"; | ||
|
||
private final RequestStorage requestStorage; | ||
private final SlackMessageGenerator slackMessageGenerator; | ||
private final PrologSlack prologSlack; | ||
|
||
public ExceptionAppender(RequestStorage requestStorage, | ||
SlackMessageGenerator slackMessageGenerator, | ||
PrologSlack prologSlack) { | ||
this.requestStorage = requestStorage; | ||
this.slackMessageGenerator = slackMessageGenerator; | ||
this.prologSlack = prologSlack; | ||
} | ||
|
||
@Before("@annotation(wooteco.prolog.common.slacklogger.SlackAlarm)") | ||
public void appendExceptionToResponseBody(JoinPoint joinPoint) { | ||
Object[] args = joinPoint.getArgs(); | ||
if (!validateHasOneArgument(args)) { | ||
return; | ||
} | ||
|
||
if (!validateIsException(args)) { | ||
return; | ||
} | ||
|
||
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); | ||
SlackAlarm annotation = signature.getMethod().getAnnotation(SlackAlarm.class); | ||
SlackAlarmErrorLevel level = annotation.level(); | ||
|
||
String message = slackMessageGenerator | ||
.generate(requestStorage.get(), (Exception) args[0], level); | ||
prologSlack.send(message); | ||
} | ||
|
||
private boolean validateIsException(Object[] args) { | ||
if (!(args[0] instanceof Exception)) { | ||
log.warn("[SlackAlarm] argument is not Exception"); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private boolean validateHasOneArgument(Object[] args) { | ||
if (args.length != 1) { | ||
log.warn(String | ||
.format(SLACK_ALARM_FORMAT, "ambiguous exceptions! require just only one Exception")); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
|
43 changes: 43 additions & 0 deletions
43
backend/src/main/java/wooteco/prolog/common/slacklogger/PrologSlack.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.reactive.function.client.WebClient; | ||
|
||
@Component | ||
public class PrologSlack { | ||
|
||
private static final String SLACK_LOGGER_WEBHOOK_URI = | ||
System.getenv("SLACK_LOGGER_WEBHOOK_URI"); | ||
|
||
public final ObjectMapper objectMapper; | ||
|
||
public PrologSlack(ObjectMapper objectMapper) { | ||
this.objectMapper = objectMapper; | ||
} | ||
|
||
public void send(String message) { | ||
WebClient.create(SLACK_LOGGER_WEBHOOK_URI) | ||
.post() | ||
.contentType(MediaType.APPLICATION_JSON) | ||
.bodyValue(toJson(message)) | ||
.retrieve() | ||
.bodyToMono(String.class) | ||
.block(); | ||
} | ||
|
||
private String toJson(String message) { | ||
try { | ||
Map<String, String> values = new HashMap<>(); | ||
values.put("text", message); | ||
|
||
return objectMapper.writeValueAsString(values); | ||
} catch (JsonProcessingException ignored) { | ||
} | ||
return "{\"text\" : \"슬랙으로 보낼 데이터를 제이슨으로 변경하는데 에러가 발생함.\"}"; | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
backend/src/main/java/wooteco/prolog/common/slacklogger/RequestStorage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
import org.springframework.web.util.ContentCachingRequestWrapper; | ||
|
||
public class RequestStorage { | ||
|
||
private ContentCachingRequestWrapper request; | ||
|
||
public void set(ContentCachingRequestWrapper request) { | ||
this.request = request; | ||
} | ||
|
||
public ContentCachingRequestWrapper get() { | ||
return request; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
backend/src/main/java/wooteco/prolog/common/slacklogger/SlackAlarm.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.METHOD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface SlackAlarm { | ||
|
||
SlackAlarmErrorLevel level() default SlackAlarmErrorLevel.WARN; | ||
} |
9 changes: 9 additions & 0 deletions
9
backend/src/main/java/wooteco/prolog/common/slacklogger/SlackAlarmErrorLevel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
public enum SlackAlarmErrorLevel { | ||
TRACE, | ||
DEBUG, | ||
INFO, | ||
WARN, | ||
ERROR | ||
} |
122 changes: 122 additions & 0 deletions
122
backend/src/main/java/wooteco/prolog/common/slacklogger/SlackMessageGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package wooteco.prolog.common.slacklogger; | ||
|
||
import static java.util.stream.Collectors.joining; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.Arrays; | ||
import java.util.Enumeration; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import org.springframework.core.env.Environment; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.util.ContentCachingRequestWrapper; | ||
import wooteco.prolog.login.application.AuthorizationExtractor; | ||
import wooteco.prolog.login.application.JwtTokenProvider; | ||
import wooteco.prolog.login.excetpion.TokenNotValidException; | ||
|
||
@Component | ||
public class SlackMessageGenerator { | ||
|
||
private static final String EXTRACTION_ERROR_MESSAGE = "메세지를 추출하는데 오류가 생겼습니다.\nmessagee : %s"; | ||
private static final String EXCEPTION_MESSAGE_FORMAT = "_%s_ %s.%s:%d - %s"; | ||
private static final String SLACK_MESSAGE_FORMAT = "*[%s]* %s\n*[요청한 멤버 id]* %s\n\n*[ERROR LOG]*\n%s\n\n*[REQUEST_INFORMATION]*\n%s %s\n%s\n\n%s"; | ||
private static final String EMPTY_BODY_MESSAGE = "{BODY IS EMPTY}"; | ||
|
||
private final Environment environment; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
public SlackMessageGenerator(Environment environment, | ||
JwtTokenProvider jwtTokenProvider) { | ||
this.environment = environment; | ||
this.jwtTokenProvider = jwtTokenProvider; | ||
} | ||
|
||
public String generate(ContentCachingRequestWrapper request, | ||
Exception exception, | ||
SlackAlarmErrorLevel level) { | ||
try { | ||
String token = AuthorizationExtractor.extract(request); | ||
String profile = getProfile(); | ||
String currentTime = getCurrentTime(); | ||
String method = request.getMethod(); | ||
String userId = getUserId(token); | ||
String requestURI = request.getRequestURI(); | ||
String headers = extractHeaders(request); | ||
String body = getBody(request); | ||
String exceptionMessage = extractExceptionMessage(exception, level); | ||
|
||
return toMessage(profile, currentTime, userId, | ||
exceptionMessage, method, requestURI, headers, body); | ||
} catch (Exception e) { | ||
return String.format(EXTRACTION_ERROR_MESSAGE, e.getMessage()); | ||
} | ||
} | ||
|
||
private String getProfile() { | ||
return String.join(",", environment.getActiveProfiles()).toUpperCase(); | ||
} | ||
|
||
private String getCurrentTime() { | ||
return LocalDateTime.now().toString(); | ||
} | ||
|
||
private String getUserId(String token) { | ||
try { | ||
return jwtTokenProvider.extractSubject(token); | ||
} catch (TokenNotValidException e) { | ||
return "Guest"; | ||
} | ||
} | ||
|
||
private String extractHeaders(ContentCachingRequestWrapper request) { | ||
Enumeration<String> headerNames = request.getHeaderNames(); | ||
|
||
Map<String, String> values = new HashMap<>(); | ||
|
||
while (headerNames.hasMoreElements()) { | ||
String headerName = headerNames.nextElement(); | ||
values.put(headerName, request.getHeader(headerName)); | ||
} | ||
|
||
return values.entrySet().stream() | ||
.map(e -> e.getKey() + ":" + e.getValue()) | ||
.collect(joining("\n")); | ||
} | ||
|
||
private String getBody(ContentCachingRequestWrapper request) { | ||
String body = new String(request.getContentAsByteArray()); | ||
if (body.isEmpty()) { | ||
body = EMPTY_BODY_MESSAGE; | ||
} | ||
return body; | ||
} | ||
|
||
private String extractExceptionMessage(Exception e, SlackAlarmErrorLevel level) { | ||
StackTraceElement stackTrace = e.getStackTrace()[0]; | ||
String className = stackTrace.getClassName(); | ||
int lineNumber = stackTrace.getLineNumber(); | ||
String methodName = stackTrace.getMethodName(); | ||
|
||
String message = e.getMessage(); | ||
|
||
if (Objects.isNull(message)) { | ||
return Arrays.stream(e.getStackTrace()) | ||
.map(StackTraceElement::toString) | ||
.collect(joining("\n")); | ||
} | ||
|
||
return String | ||
.format(EXCEPTION_MESSAGE_FORMAT, level.name(), className, methodName, lineNumber, | ||
message); | ||
} | ||
|
||
|
||
private String toMessage(String profile, String currentTime, String userId, String errorMessage, | ||
String method, String requestURI, String headers, String body) { | ||
return String.format( | ||
SLACK_MESSAGE_FORMAT, profile, currentTime, userId, | ||
errorMessage, method, requestURI, headers, body | ||
); | ||
} | ||
} |
5 changes: 0 additions & 5 deletions
5
backend/src/main/resources/db/migration/prod/V6__add_create_and_update_time_for_report.sql
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.