Skip to content
Merged
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
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ dependencies {
// prometheus
implementation("org.springframework.boot:spring-boot-starter-actuator")
runtimeOnly("io.micrometer:micrometer-registry-prometheus")

// slack
implementation("com.slack.api:bolt:1.44.2")
implementation("com.slack.api:bolt-servlet:1.44.2")
implementation("com.slack.api:bolt-jetty:1.44.2")
}

def querydslSrcDir = 'src/main/generated'
Expand Down
102 changes: 49 additions & 53 deletions src/main/java/com/avab/avab/apiPayload/exception/ExceptionAdvice.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.avab.avab.apiPayload.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

Expand All @@ -28,8 +24,6 @@
import com.avab.avab.apiPayload.BaseResponse;
import com.avab.avab.apiPayload.code.ErrorReasonDTO;
import com.avab.avab.apiPayload.code.status.ErrorStatus;
import com.avab.avab.feign.discord.dto.DiscordMessage;
import com.avab.avab.feign.discord.dto.DiscordMessage.Embed;
import com.avab.avab.feign.discord.service.DiscordClient;
import com.avab.avab.utils.EnvironmentHelper;

Expand All @@ -43,6 +37,7 @@ public class ExceptionAdvice extends ResponseEntityExceptionHandler {

private final DiscordClient discordClient;
private final EnvironmentHelper environmentHelper;
private final ExceptionNotifier exceptionNotifier;

@Override
protected ResponseEntity<Object> handleTypeMismatch(
Expand Down Expand Up @@ -109,10 +104,11 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(

@ExceptionHandler
public ResponseEntity<Object> exception(Exception e, WebRequest request) {
e.printStackTrace();
log.error("알 수 없는 예외 발생", e);

if (environmentHelper.isLocal()) {
sendDiscordAlarm(e, request);
if (!environmentHelper.isLocal()) {
// sendDiscordAlarm(e, request);
exceptionNotifier.notify(e, request);
}

return handleExceptionInternalFalse(
Expand Down Expand Up @@ -187,48 +183,48 @@ private ResponseEntity<Object> handleExceptionInternalMessage(
return super.handleExceptionInternal(
e, body, headers, errorStatus.getHttpStatus(), request);
}

private void sendDiscordAlarm(Exception e, WebRequest request) {
discordClient.sendAlarm(createMessage(e, request));
}

private DiscordMessage createMessage(Exception e, WebRequest request) {
return DiscordMessage.builder()
.content("# 🚨 에러 발생 비이이이이사아아아앙")
.embeds(
List.of(
Embed.builder()
.title("ℹ️ 에러 정보")
.description(
"### 🕖 발생 시간\n"
+ LocalDateTime.now()
+ "\n"
+ "### 🔗 요청 URL\n"
+ createRequestFullPath(request)
+ "\n"
+ "### 📄 Stack Trace\n"
+ "```\n"
+ getStackTrace(e).substring(0, 1000)
+ "\n```")
.build()))
.build();
}

private String createRequestFullPath(WebRequest webRequest) {
HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest();
String fullPath = request.getMethod() + " " + request.getRequestURL();

String queryString = request.getQueryString();
if (queryString != null) {
fullPath += "?" + queryString;
}

return fullPath;
}

private String getStackTrace(Exception e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
//
// private void sendDiscordAlarm(Exception e, WebRequest request) {
// discordClient.sendAlarm(createMessage(e, request));
// }
//
// private DiscordMessage createMessage(Exception e, WebRequest request) {
// return DiscordMessage.builder()
// .content("# 🚨 에러 발생 비이이이이사아아아앙")
// .embeds(
// List.of(
// Embed.builder()
// .title("ℹ️ 에러 정보")
// .description(
// "### 🕖 발생 시간\n"
// + LocalDateTime.now()
// + "\n"
// + "### 🔗 요청 URL\n"
// + createRequestFullPath(request)
// + "\n"
// + "### 📄 Stack Trace\n"
// + "```\n"
// + getStackTrace(e).substring(0, 1000)
// + "\n```")
// .build()))
// .build();
// }
//
// private String createRequestFullPath(WebRequest webRequest) {
// HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest();
// String fullPath = request.getMethod() + " " + request.getRequestURL();
//
// String queryString = request.getQueryString();
// if (queryString != null) {
// fullPath += "?" + queryString;
// }
//
// return fullPath;
// }
//
// private String getStackTrace(Exception e) {
// StringWriter stringWriter = new StringWriter();
// e.printStackTrace(new PrintWriter(stringWriter));
// return stringWriter.toString();
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.avab.avab.apiPayload.exception;

import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.*;
import static com.slack.api.model.block.element.BlockElements.*;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import jakarta.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;

import com.avab.avab.slack.SlackChannel;
import com.avab.avab.slack.SlackService;
import com.slack.api.model.block.LayoutBlock;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class ExceptionNotifier {
private final SlackService slackService;

public void notify(Exception e, WebRequest request) {
slackService.sendMessage(SlackChannel.SERVER_ERROR, createMessage(e, request));
}

private List<LayoutBlock> createMessage(Exception e, WebRequest request) {
return asBlocks(
// 타이틀
header(h -> h.text(plainText(pt -> pt.text("🚨 에러 발생")))),

// 구분선
divider(),

// 에러 정보
section(section -> section.text(markdownText("*ℹ️ 에러 정보*"))),
// 발생 시간
section(section -> section.text(markdownText("*🕖 발생 시간*"))),
section(
section ->
section.text(
markdownText(
DateTimeFormatter.ofPattern(
"yyyy-MM-dd HH:mm:ss:SSS")
.format(LocalDateTime.now())))),

// 요청 URL
section(section -> section.text(markdownText("*🔗 요청 URL*"))),
section(section -> section.text(markdownText(createRequestFullPath(request)))),

// Stack Trace
section(section -> section.text(markdownText("*📄 Stack Trace*"))),
section(
section ->
section.text(
markdownText(
"```\n"
+ getStackTrace(e).substring(0, 1000)
+ "\n```"))));
}

private String createRequestFullPath(WebRequest webRequest) {
HttpServletRequest request = ((ServletWebRequest) webRequest).getRequest();
String fullPath = request.getMethod() + " " + request.getRequestURL();

String queryString = request.getQueryString();
if (queryString != null) {
fullPath += "?" + queryString;
}

return fullPath;
}

private String getStackTrace(Exception e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/avab/avab/slack/SlackChannel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.avab.avab.slack;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum SlackChannel {
SERVER_ERROR("C086U3W5KPU");

private final String channelId;
}
36 changes: 36 additions & 0 deletions src/main/java/com/avab/avab/slack/SlackService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.avab.avab.slack;

import java.util.List;

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

import com.slack.api.Slack;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.model.block.LayoutBlock;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class SlackService {
@Value("${slack.token}")
private String token;

public void sendMessage(SlackChannel channel, List<LayoutBlock> message) {
try {
ChatPostMessageRequest request =
ChatPostMessageRequest.builder()
.channel(channel.getChannelId())
.blocks(message)
.build();

ChatPostMessageResponse response =
Slack.getInstance().methods(token).chatPostMessage(request);
System.out.println(response.getMessage());
} catch (Exception e) {
log.error("슬랙 메시지 전송 실패: {}", e.getMessage());
}
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ management:
http-basic:
username: ${ACTUATOR_USERNAME} # 액츄에이터 http basic 인증 사용자 이름
password: ${ACTUATOR_PASSWORD} # 액츄에이터 http basic 인증 비밀번호

# slack
slack:
token: ${SLACK_TOKEN}
---
# Local Profile
spring:
Expand Down
Loading