Skip to content
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
implementation 'org.projectlombok:lombok:1.18.26'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'mysql:mysql-connector-java'
implementation 'com.slack.api:slack-api-client:1.29.2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation "com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.posomo.saltit.domain.exception;

import com.posomo.saltit.global.constant.ErrorMessage;

public class SlackMessageException extends RuntimeException {
public SlackMessageException(String message, Throwable cause) {
super(message, cause);
}

public SlackMessageException(String message) {
super(message);
}

public SlackMessageException() {
super(ErrorMessage.SLACK_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.posomo.saltit.global.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public interface ErrorMessageSenderAdvice {

public Object sendError(ProceedingJoinPoint joinPoint) throws Throwable;
}
51 changes: 51 additions & 0 deletions src/main/java/com/posomo/saltit/global/aop/SlackMessageAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.posomo.saltit.global.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import com.posomo.saltit.service.MessageSender;

import lombok.RequiredArgsConstructor;

@Aspect
@Component
@RequiredArgsConstructor
public class SlackMessageAdvice implements ErrorMessageSenderAdvice {

private final MessageSender messageSender;

@Override
@Around(value = "execution(public * com.posomo.saltit.global.aop.ControllerAdvice.*(..))")
public Object sendError(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args.length == 1 && args[0] instanceof Throwable exception) {
StringBuilder stringBuilder = buildMessage(exception);

messageSender.sendMessage(stringBuilder.toString());
}
return joinPoint.proceed();
}

@NotNull
private static StringBuilder buildMessage(Throwable exception) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Transaction Id\n");
stringBuilder.append(MDC.get("transactionId"));
stringBuilder.append("\n\n");

stringBuilder.append("Error Message\n");
stringBuilder.append(exception.getMessage());
stringBuilder.append("\n\n");

stringBuilder.append("Stack Trace\n");
for (StackTraceElement element : exception.getStackTrace()) {
stringBuilder.append(element.toString());
stringBuilder.append("\n");
}
return stringBuilder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ private ErrorMessage() {

public static final String UNKNOWN_ERROR = "์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
public static final String RECORD_NOT_FOUND = "ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.";
public static final String SLACK_ERROR = "Slack ๋ฉ”์‹œ์ง€ ์ „์†ก์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
package com.posomo.saltit.global.filter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import com.posomo.saltit.service.MessageSender;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Component
@RequiredArgsConstructor
@Log4j2
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {

private final MessageSender messageSender;

private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
MediaType.valueOf("text/*"),
Expand Down Expand Up @@ -62,13 +65,16 @@ protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCach
try {
beforeRequest(request, msg);
filterChain.doFilter(request, response);
}
finally {
} finally {
afterRequest(request, response, msg);
if(log.isInfoEnabled()) {
log.info(msg.toString());
String msgString = msg.toString();
if (log.isInfoEnabled()) {
log.info(msgString);
}
response.copyBodyToResponse();
if (response.getStatus() >= 400) {
messageSender.sendMessage("Transaction Id\n" + MDC.get("transactionId") + "\n\n" + msgString);
}
}
}

Expand Down Expand Up @@ -98,10 +104,9 @@ private static void logRequestHeader(ContentCachingRequestWrapper request, Strin
.forEach(headerName ->
Collections.list(request.getHeaders(headerName))
.forEach(headerValue -> {
if(isSensitiveHeader(headerName)) {
if (isSensitiveHeader(headerName)) {
msg.append(String.format("%s %s: %s", prefix, headerName, "*******")).append("\n");
}
else {
} else {
msg.append(String.format("%s %s: %s", prefix, headerName, headerValue)).append("\n");
}
}));
Expand All @@ -123,10 +128,9 @@ private static void logResponse(ContentCachingResponseWrapper response, String p
response.getHeaders(headerName)
.forEach(headerValue ->
{
if(isSensitiveHeader(headerName)) {
if (isSensitiveHeader(headerName)) {
msg.append(String.format("%s %s: %s", prefix, headerName, "*******")).append("\n");
}
else {
} else {
msg.append(String.format("%s %s: %s", prefix, headerName, headerValue)).append("\n");
}
}));
Expand Down Expand Up @@ -158,16 +162,16 @@ private static boolean isSensitiveHeader(String headerName) {
}

private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
if (request instanceof ContentCachingRequestWrapper requestWrapper) {
return requestWrapper;
} else {
return new ContentCachingRequestWrapper(request);
}
}

private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
if (response instanceof ContentCachingResponseWrapper responseWrapper) {
return responseWrapper;
} else {
return new ContentCachingResponseWrapper(response);
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/posomo/saltit/service/MessageSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.posomo.saltit.service;

import com.posomo.saltit.domain.exception.SlackMessageException;

public interface MessageSender {
void sendMessage(String channel, String text) throws SlackMessageException;

void sendMessage(String text) throws SlackMessageException;

void setToken(String token);
}
45 changes: 45 additions & 0 deletions src/main/java/com/posomo/saltit/service/SlackMessageSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.posomo.saltit.service;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.posomo.saltit.domain.exception.SlackMessageException;
import com.slack.api.Slack;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;

@Service
public class SlackMessageSender implements MessageSender {

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

@Value("${slack.bot.default-channel}")
private String channel;

@Override
public void sendMessage(String channel, String text) throws SlackMessageException {
try {
ChatPostMessageResponse response = Slack.getInstance().methods()
.chatPostMessage(req -> req.token(token).channel(channel).text(text));
if (!response.isOk()) {
String errorCode = response.getError();
throw new SlackMessageException(errorCode);
}
} catch (SlackApiException | IOException e) {
throw new SlackMessageException(e.getMessage(), e);
}
}

@Override
public void sendMessage(String text) throws SlackMessageException{
sendMessage(this.channel, text);
}

@Override
public void setToken(String token) {
this.token = token;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.posomo.saltit.domain.exception.NoRecordException;
import com.posomo.saltit.domain.restaurant.dto.RestaurantDetailResponse;
import com.posomo.saltit.global.constant.ResponseMessage;
import com.posomo.saltit.service.MessageSender;
import com.posomo.saltit.service.RestaurantService;

@WebMvcTest(RestaurantControllerV1.class)
Expand All @@ -31,6 +32,9 @@ class RestaurantControllerV1Test {
@MockBean
RestaurantService restaurantService;

@MockBean
MessageSender messageSender;

@BeforeEach
void setRestaurantServiceStub() {
List<RestaurantDetailResponse.Classification.Menu> mainMenus = new ArrayList<>();
Expand All @@ -45,8 +49,7 @@ void setRestaurantServiceStub() {
RestaurantDetailResponse.Classification main = new RestaurantDetailResponse.Classification(3, mainMenus);
RestaurantDetailResponse.Classification side = new RestaurantDetailResponse.Classification(2, sideMenus);
RestaurantDetailResponse restaurantDetailResponse = new RestaurantDetailResponse(1L, "testUrl",
5, "test store", 100, "phone", "address", new ArrayList<>(), main, side,"testImageUrl");

5, "test store", 100, "phone", "address", new ArrayList<>(), main, side, "testImageUrl");

when(restaurantService.getRestaurantDetail(1L)).thenReturn(restaurantDetailResponse);
when(restaurantService.getRestaurantDetail(2L)).thenThrow(new NoRecordException(String.format("restaurantId = %d record not found", 2L)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.posomo.saltit.service;

import static org.assertj.core.api.Assertions.*;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.posomo.saltit.domain.exception.SlackMessageException;

@SpringBootTest
class SlackMessageSenderTest {

@Autowired
SlackMessageSender slackMessageSender;


@DisplayName("Slack์— ๋ฉ”์‹œ์ง€ ๋ณด๋‚ด๊ธฐ ํ…Œ์ŠคํŠธ")
@Nested
class sendMessage {
@DisplayName("์„ฑ๊ณต ์ผ€์ด์Šค")
@Test
void ok() {
Assertions.assertDoesNotThrow(() -> slackMessageSender.sendMessage("testMessage"));
}

@DisplayName("์ž˜๋ชป๋œ ํ† ํฐ ์‚ฌ์šฉ")
@Test
void wrongToken() {
slackMessageSender.setToken("wrong_token");
Assertions.assertThrows(SlackMessageException.class, () -> slackMessageSender.sendMessage("wrong_token_test"));
try {
slackMessageSender.sendMessage("wrong_token_test");
} catch (SlackMessageException e) {
assertThat(e.getMessage()).isEqualTo("invalid_auth");
}
}

@DisplayName("์ž˜๋ชป๋œ ์ฑ„๋„๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก")
@Test
void wrongChannel() {
Assertions.assertThrows(SlackMessageException.class, () -> slackMessageSender.sendMessage("wrong_channel_test1", "wrong_channel_test"));
try {
slackMessageSender.sendMessage("wrong_channel_test1", "wrong_channel_test");
} catch (SlackMessageException e) {
System.out.println(e.getMessage());
assertThat(e.getMessage()).isEqualTo("channel_not_found");
}
}
}

}