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
4 changes: 3 additions & 1 deletion src/main/java/org/sopt/makers/dto/SentryEventDetail.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public record SentryEventDetail(
String webUrl,
String message,
String datetime,
String level
String level,
String title
) {
public static SentryEventDetail from(JsonNode eventNode) {
return SentryEventDetail.builder()
Expand All @@ -20,6 +21,7 @@ public static SentryEventDetail from(JsonNode eventNode) {
.message(eventNode.path("message").asText())
.datetime(eventNode.path("datetime").asText())
.level(eventNode.path("level").asText())
.title(eventNode.path("title").asText())
.build();
}
}
61 changes: 34 additions & 27 deletions src/main/java/org/sopt/makers/service/SlackNotificationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
import org.sopt.makers.global.exception.message.ErrorMessage;
import org.sopt.makers.global.exception.unchecked.HttpRequestException;
import org.sopt.makers.global.util.HttpClientUtil;
import org.sopt.makers.vo.slack.message.SlackMessage;
import org.sopt.makers.vo.slack.block.ActionsBlock;
import org.sopt.makers.vo.slack.block.Block;
import org.sopt.makers.vo.slack.block.HeaderBlock;
import org.sopt.makers.vo.slack.block.SectionBlock;
import org.sopt.makers.vo.slack.element.Button;
import org.sopt.makers.vo.slack.message.SlackMessage;
import org.sopt.makers.vo.slack.text.MarkdownText;
import org.sopt.makers.vo.slack.text.Text;

import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -54,8 +55,8 @@ private SlackMessage createSlackMessage(String team, String type, String stage,
try {
return buildSlackMessage(team, type, stage, sentryEventDetail);
} catch (DateTimeException e) {
log.error("Slack 메시지 생성 실패: team={}, type={}, stage={}, id={}, error={}",
team, type, stage, sentryEventDetail.issueId(), e.getMessage(), e);
log.error("Slack 메시지 생성 실패: team={}, type={}, stage={}, id={}, error={}", team, type, stage,
sentryEventDetail.issueId(), e.getMessage(), e);
throw SlackMessageBuildException.from(ErrorMessage.SLACK_MESSAGE_BUILD_FAILED);
}
}
Expand All @@ -74,17 +75,17 @@ private void sendSlackMessage(SlackMessage slackMessage, String webhookUrl, Stri
}
}

private void handleSlackResponse(HttpResponse<String> response, String team, String type,
String stage, SentryEventDetail sentryEventDetail) throws SlackSendException {
private void handleSlackResponse(HttpResponse<String> response, String team, String type, String stage,
SentryEventDetail sentryEventDetail) throws SlackSendException {
if (response.statusCode() != 200 || !"ok".equalsIgnoreCase(response.body())) {
String errorMsg = String.format("Slack API 응답 오류, status: %d, body: %s",
response.statusCode(), response.body());
String errorMsg = String.format("Slack API 응답 오류, status: %d, body: %s", response.statusCode(),
response.body());
log.error("{}", errorMsg);
throw SlackSendException.from(ErrorMessage.SLACK_SEND_FAILED);
}

log.info("[Slack 전송 완료] team={}, type={}, stage={}, id={}, statusCode={}",
team, type, stage, sentryEventDetail.issueId(), response.statusCode());
log.info("[Slack 전송 완료] team={}, type={}, stage={}, id={}, statusCode={}", team, type, stage,
sentryEventDetail.issueId(), response.statusCode());
}

/**
Expand All @@ -96,9 +97,10 @@ private SlackMessage buildSlackMessage(String team, String type, String stage,
String color = Color.getColorByLevel(sentryEventDetail.level());

List<Block> blocks = new ArrayList<>();
blocks.add(buildHeaderBlock(sentryEventDetail.level()));
blocks.add(buildDetailsBlock(team, type, stage, formattedDate));
blocks.add(buildMessageBlock(sentryEventDetail.message()));
blocks.add(buildHeaderBlock(sentryEventDetail.message()));
blocks.add(buildDetailsBlock(team, type, stage, formattedDate, sentryEventDetail.issueId(),
sentryEventDetail.level()));
blocks.add(buildMessageBlock(sentryEventDetail.title()));
blocks.add(buildActionsBlock(sentryEventDetail.webUrl()));

return SlackMessage.newInstance(blocks, color);
Expand All @@ -107,30 +109,36 @@ private SlackMessage buildSlackMessage(String team, String type, String stage,
/**
* 헤더 블록 구성
*/
private Block buildHeaderBlock(String level) {
return HeaderBlock.newInstance(String.format("[%s] 오류 발생", level.toUpperCase()));
private Block buildHeaderBlock(String message) {
return HeaderBlock.newInstance(message);
}

/**
* 상세 정보 블록 구성
*/
private SectionBlock buildDetailsBlock(String team, String type, String stage, String date) {
List<Text> fields = List.of(
Text.newFieldInstance(String.format("*환경:*%s%s", NEW_LINE, stage)),
Text.newFieldInstance(String.format("*팀:*%s%s", NEW_LINE, team)),
Text.newFieldInstance(String.format("*유형:*%s%s", NEW_LINE, type)),
Text.newFieldInstance(String.format("*발생 시간:*%s%s", NEW_LINE, date))
);
private Block buildDetailsBlock(String team, String type, String stage, String date, String issueId, String level) {
List<Text> fields = List.of(MarkdownText.newInstance(String.format("*Environment:*%s%s", NEW_LINE, stage)),
MarkdownText.newInstance(String.format("*Team:*%s%s", NEW_LINE, team)),
MarkdownText.newInstance(String.format("*Server Type:*%s%s", NEW_LINE, type)),
MarkdownText.newInstance(String.format("*Issue Id:*%s%s", NEW_LINE, issueId)),
MarkdownText.newInstance(String.format("*Happen:*%s%s", NEW_LINE, date)),
MarkdownText.newInstance(String.format("*Level:*%s%s", NEW_LINE, level)));

return SectionBlock.newInstanceWithFields(fields);
}

/**
* 메시지 블록 구성
*/
private Block buildMessageBlock(String message) {
return SectionBlock.newInstanceWithText(
Text.newFieldInstance(String.format("*오류 메시지:*%s%s", NEW_LINE, message))
);
private Block buildMessageBlock(String title) {
Text text = MarkdownText.newInstance("""
*Error Details:*
```
%s
```
""".formatted(title.trim()));

return SectionBlock.newInstanceWithText(text);
}

/**
Expand All @@ -145,8 +153,7 @@ private Block buildActionsBlock(String webUrl) {
*/
private String formatDateTime(String isoDatetime) {
OffsetDateTime utcTime = OffsetDateTime.parse(isoDatetime, DateTimeFormatter.ISO_DATE_TIME);
LocalDateTime koreaTime = utcTime.atZoneSameInstant(ZoneId.of(TIMEZONE_SEOUL))
.toLocalDateTime();
LocalDateTime koreaTime = utcTime.atZoneSameInstant(ZoneId.of(TIMEZONE_SEOUL)).toLocalDateTime();
return koreaTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT_PATTERN));
}
}
7 changes: 4 additions & 3 deletions src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package org.sopt.makers.vo.slack.block;

import org.sopt.makers.vo.slack.text.Text;

import static org.sopt.makers.global.constant.SlackConstant.*;

import org.sopt.makers.vo.slack.text.PlainText;
import org.sopt.makers.vo.slack.text.Text;

public record HeaderBlock(
String type,
Text text
) implements Block {
public static HeaderBlock newInstance(String text) {
return new HeaderBlock(BLOCK_TYPE_HEADER, Text.newPlainInstance(text, true));
return new HeaderBlock(BLOCK_TYPE_HEADER, PlainText.newInstance("🚨 " + text, true));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.sopt.makers.vo.slack.block;

import org.sopt.makers.vo.slack.text.Text;
import static org.sopt.makers.global.constant.SlackConstant.*;

import java.util.List;

import org.sopt.makers.vo.slack.text.Text;

public record SectionBlock(
String type,
List<Text> fields,
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/sopt/makers/vo/slack/element/Button.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package org.sopt.makers.vo.slack.element;

import org.sopt.makers.vo.slack.text.Text;
import static org.sopt.makers.global.constant.SlackConstant.*;

import org.sopt.makers.vo.slack.text.PlainText;
import org.sopt.makers.vo.slack.text.Text;

public record Button(
String type,
Text text,
String style,
String url
) implements Element {
public static Button newInstance(String text, String url) {
return new Button(ELEMENT_TYPE_BUTTON, Text.newPlainInstance(text, true), STYLE_PRIMARY, url);
return new Button(ELEMENT_TYPE_BUTTON, PlainText.newInstance(text, true), STYLE_PRIMARY, url);
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/sopt/makers/vo/slack/text/MarkdownText.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.makers.vo.slack.text;

import static org.sopt.makers.global.constant.SlackConstant.*;

public record MarkdownText(
String type,
String text
) implements Text {
public static MarkdownText newInstance(String text) {
return new MarkdownText(TEXT_TYPE_MARKDOWN, text);
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/sopt/makers/vo/slack/text/PlainText.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.makers.vo.slack.text;

import static org.sopt.makers.global.constant.SlackConstant.*;

public record PlainText(
String type,
String text,
boolean emoji
) implements Text {
public static PlainText newInstance(String text, boolean emoji) {
return new PlainText(TEXT_TYPE_PLAIN, text, emoji);
}
}
17 changes: 3 additions & 14 deletions src/main/java/org/sopt/makers/vo/slack/text/Text.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
package org.sopt.makers.vo.slack.text;

import static org.sopt.makers.global.constant.SlackConstant.*;

public record Text(
String type,
String text,
boolean emoji
) {
public static Text newPlainInstance(String text, boolean emoji) {
return new Text(TEXT_TYPE_PLAIN, text, emoji);
}

public static Text newFieldInstance(String text) {
return new Text(TEXT_TYPE_MARKDOWN, text, false);
}
public sealed interface Text permits PlainText, MarkdownText {
String type();
String text();
}
Loading