Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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 +110,11 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(

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

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

return handleExceptionInternalFalse(
Expand Down
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