Skip to content

Commit

Permalink
Merge pull request #191 from Team-HMH/feat/#160-slack-alarm
Browse files Browse the repository at this point in the history
feat - #160 μŠ¬λž™ μ•ŒλžŒ 연동
  • Loading branch information
jumining authored Oct 8, 2024
2 parents 55ecaf1 + e79985b commit f19afda
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 8 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ dependencies {
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

// Slack
implementation 'com.slack.api:slack-api-client:1.27.2'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public LoginResponse signup(SocialSignUpRequest request, String socialAccessToke
SocialPlatform socialPlatform = request.socialPlatform();
String socialId = this.getSocialIdBySocialAccessToken(socialPlatform, socialAccessToken);

User newUser = userService.addUser(socialPlatform, socialId, request.name());
User newUser = userService.addUser(socialPlatform, socialId, request.name(), os);
Long newUserId = newUser.getId();

userService.registerOnboardingInfo(request, newUserId);
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/sopt/org/hmh/domain/slack/SlackSender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sopt.org.hmh.domain.slack;

import com.slack.api.Slack;
import com.slack.api.model.Attachment;
import com.slack.api.webhook.WebhookPayloads;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class SlackSender {

private final Slack slackClient = Slack.getInstance();

public void sendSlackNotification(String webhookUrl, String title, Attachment attachment) {
try {
slackClient.send(webhookUrl, WebhookPayloads.payload(p -> p
.text(title)
.attachments(List.of(attachment))
));
} catch (IOException slackError) {
log.debug("Slack 톡신 μ˜ˆμ™Έ λ°œμƒ: {}", slackError.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package sopt.org.hmh.domain.slack.builder;

import com.slack.api.model.Attachment;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import sopt.org.hmh.domain.slack.SlackSender;
import sopt.org.hmh.domain.slack.constant.SlackStatus;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

@Component
@RequiredArgsConstructor
public class ErrorSlackMessageBuilder implements SlackMessageBuilder {

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

private final SlackSender slackSender;

public void sendNotification(SlackStatus status, Exception e, HttpServletRequest request) {
slackSender.sendSlackNotification(serverErrorWebUrl, status.getTitle(),
generateSlackAttachment(status, e, request));
}

@Override
public Attachment generateSlackAttachment(SlackStatus status, Object... params) {
Exception e = (Exception) params[0];
HttpServletRequest request = (HttpServletRequest) params[1];

String requestTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(LocalDateTime.now());

return Attachment.builder()
.color(changeColorToHex(status.getColor()))
.title(requestTime + " λ°œμƒ 였λ₯˜ 둜그")
.fields(List.of(
generateSlackField("User Info", "- ID : " + request.getRemoteUser()),
generateSlackField("Request URL", "[" + request.getMethod() + "] " + request.getRequestURI()),
generateSlackField("Error Info", e.getMessage())))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package sopt.org.hmh.domain.slack.builder;

import com.slack.api.model.Attachment;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import sopt.org.hmh.domain.slack.SlackSender;
import sopt.org.hmh.domain.slack.constant.SlackStatus;
import sopt.org.hmh.domain.user.repository.UserRepository;

import java.util.List;

@Component
@RequiredArgsConstructor
public class NewUserSlackMessageBuilder implements SlackMessageBuilder{

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

private final SlackSender slackSender;
private final UserRepository userRepository;

public void sendNotification(SlackStatus status, String userName, String os) {
slackSender.sendSlackNotification(
newUserWebUrl,
status.getTitle(),
generateSlackAttachment(status, userName, os));
}

@Override
public Attachment generateSlackAttachment(SlackStatus status, Object... params) {
String userName = (String) params[0];
String os = (String) params[1];

return Attachment.builder()
.color(changeColorToHex(status.getColor()))
.title("μƒˆλ‘œμš΄ μœ μ € '" + userName + "'λ‹˜μ΄ κ°€μž…ν–ˆμŠ΅λ‹ˆλ‹€!")
.fields(List.of(
generateSlackField("총 μœ μ € 수", " πŸ‘‰ " + userRepository.count() + "λͺ…"),
generateSlackField("κ°€μž…ν•œ OS", "πŸ‘‰ " + os)))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package sopt.org.hmh.domain.slack.builder;

import com.slack.api.model.Attachment;
import com.slack.api.model.Field;
import sopt.org.hmh.domain.slack.constant.SlackStatus;

public interface SlackMessageBuilder {

Attachment generateSlackAttachment(SlackStatus status, Object... params);

default String changeColorToHex(java.awt.Color color) {
return String.format("%06X", (0xFFFFFF & color.getRGB()));
}

default Field generateSlackField(String title, String value) {
return Field.builder()
.title(title)
.value(value)
.valueShortEnough(false)
.build();
}
}
15 changes: 15 additions & 0 deletions src/main/java/sopt/org/hmh/domain/slack/constant/SlackStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package sopt.org.hmh.domain.slack.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum SlackStatus {
NEW_USER_SIGNUP(java.awt.Color.GREEN, "πŸŽ‰ μ‹ κ·œ μœ μ € νšŒμ› κ°€μž… λ°œμƒ"),
INTERNAL_ERROR(java.awt.Color.ORANGE, "🚨 μ„œλ²„ λ‚΄λΆ€ μ—λŸ¬ λ°œμƒ"),
CLIENT_ERROR(java.awt.Color.RED, "😭 μ„œλ²„ 였λ₯˜ λ°œμƒ");

private final java.awt.Color color;
private final String title;
}
10 changes: 7 additions & 3 deletions src/main/java/sopt/org/hmh/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import sopt.org.hmh.domain.auth.exception.AuthException;
import sopt.org.hmh.domain.auth.repository.OnboardingInfoRepository;
import sopt.org.hmh.domain.auth.repository.ProblemRepository;
import sopt.org.hmh.domain.slack.builder.NewUserSlackMessageBuilder;
import sopt.org.hmh.domain.slack.constant.SlackStatus;
import sopt.org.hmh.domain.user.domain.User;
import sopt.org.hmh.domain.user.domain.UserConstants;
import sopt.org.hmh.domain.user.domain.exception.UserError;
Expand All @@ -29,6 +31,7 @@ public class UserService {
private final UserRepository userRepository;
private final OnboardingInfoRepository onboardingInfoRepository;
private final ProblemRepository problemRepository;
private final NewUserSlackMessageBuilder newUserSlackMessageBuilder;

@Transactional
public void withdraw(Long userId) {
Expand Down Expand Up @@ -56,16 +59,17 @@ public void validateDuplicateUser(String socialId, SocialPlatform socialPlatform
}
}

public User addUser(SocialPlatform socialPlatform, String socialId, String name) {
public User addUser(SocialPlatform socialPlatform, String socialId, String name, String os) {
this.validateDuplicateUser(socialId, socialPlatform);

return userRepository.save(
User user = userRepository.save(
User.builder()
.socialPlatform(socialPlatform)
.socialId(socialId)
.name(validateName(name))
.build()
);
newUserSlackMessageBuilder.sendNotification(SlackStatus.NEW_USER_SIGNUP, name, os);
return user;
}

private String validateName(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.io.PrintWriter;

import sopt.org.hmh.domain.slack.builder.ErrorSlackMessageBuilder;
import sopt.org.hmh.domain.slack.constant.SlackStatus;
import sopt.org.hmh.global.auth.jwt.JwtConstants;
import sopt.org.hmh.global.auth.jwt.exception.JwtError;
import sopt.org.hmh.global.auth.jwt.exception.JwtException;
import sopt.org.hmh.global.common.exception.base.ErrorBase;
import sopt.org.hmh.global.common.response.BaseResponse;

@Slf4j
@RequiredArgsConstructor
@Component
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper = new ObjectMapper();
private final ErrorSlackMessageBuilder errorSlackMessageBuilder;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
Expand All @@ -28,7 +36,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
} catch (JwtException error) {
handleUnauthorizedException(response, error.getError());
} catch (Exception error) {
handleException(response, error);
handleException(response, request, error);
}
}

Expand All @@ -38,9 +46,10 @@ private void handleUnauthorizedException(HttpServletResponse response, ErrorBase
setResponse(response, httpStatus, jwtError);
}

private void handleException(HttpServletResponse response, Exception e) throws IOException {
private void handleException(HttpServletResponse response, HttpServletRequest request, Exception e) throws IOException {
log.error(">>> Exception Handler Filter : ", e);
setResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, JwtError.INTERNAL_SERVER_ERROR);
errorSlackMessageBuilder.sendNotification(SlackStatus.INTERNAL_ERROR, e, request);
}

private void setResponse(HttpServletResponse response, HttpStatus httpStatus, ErrorBase errorMessage) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package sopt.org.hmh.global.common.exception;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import sopt.org.hmh.domain.slack.builder.ErrorSlackMessageBuilder;
import sopt.org.hmh.domain.slack.constant.SlackStatus;
import sopt.org.hmh.global.common.exception.base.ErrorBase;
import sopt.org.hmh.global.common.exception.base.ExceptionBase;
import sopt.org.hmh.global.common.response.BaseResponse;
Expand All @@ -13,8 +16,12 @@
@RequiredArgsConstructor
public class CommonControllerAdvice {

private final ErrorSlackMessageBuilder errorSlackMessageBuilder;

@ExceptionHandler(value = ExceptionBase.class)
public ResponseEntity<?> exceptionHandler(HttpServletResponse response, ExceptionBase exception) {
public ResponseEntity<?> exceptionHandler(HttpServletRequest request, HttpServletResponse response, ExceptionBase exception) {
errorSlackMessageBuilder.sendNotification(SlackStatus.CLIENT_ERROR, exception, request);

ErrorBase error = exception.getError();
response.setStatus(error.getHttpStatusCode());
return ResponseEntity
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/sopt/org/hmh/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import sopt.org.hmh.domain.slack.builder.ErrorSlackMessageBuilder;
import sopt.org.hmh.global.auth.jwt.service.JwtProvider;
import sopt.org.hmh.global.auth.jwt.service.JwtValidator;
import sopt.org.hmh.global.auth.security.JwtAuthenticationEntryPoint;
Expand All @@ -24,6 +25,7 @@ public class SecurityConfig {
private final JwtValidator jwtValidator;
private final JwtProvider jwtProvider;
private final JwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;
private final ErrorSlackMessageBuilder errorSlackMessageBuilder;

private static final String[] AUTH_WHITELIST = {
// Global
Expand Down Expand Up @@ -61,7 +63,7 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
authorizationManagerRequestMatcherRegistry
.anyRequest().authenticated())
.addFilterBefore(new JwtAuthenticationFilter(jwtValidator, jwtProvider), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class)
.addFilterBefore(new ExceptionHandlerFilter(errorSlackMessageBuilder), JwtAuthenticationFilter.class)
.build();
}

Expand Down

0 comments on commit f19afda

Please sign in to comment.