diff --git a/src/main/java/org/sopt/makers/dto/SentryEventDetail.java b/src/main/java/org/sopt/makers/dto/SentryEventDetail.java index 845f8fc..7ee1f17 100644 --- a/src/main/java/org/sopt/makers/dto/SentryEventDetail.java +++ b/src/main/java/org/sopt/makers/dto/SentryEventDetail.java @@ -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() @@ -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(); } } diff --git a/src/main/java/org/sopt/makers/service/SlackNotificationService.java b/src/main/java/org/sopt/makers/service/SlackNotificationService.java index 352d3bd..47c0dbf 100644 --- a/src/main/java/org/sopt/makers/service/SlackNotificationService.java +++ b/src/main/java/org/sopt/makers/service/SlackNotificationService.java @@ -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; @@ -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); } } @@ -74,17 +75,17 @@ private void sendSlackMessage(SlackMessage slackMessage, String webhookUrl, Stri } } - private void handleSlackResponse(HttpResponse response, String team, String type, - String stage, SentryEventDetail sentryEventDetail) throws SlackSendException { + private void handleSlackResponse(HttpResponse 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()); } /** @@ -96,9 +97,10 @@ private SlackMessage buildSlackMessage(String team, String type, String stage, String color = Color.getColorByLevel(sentryEventDetail.level()); List 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); @@ -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 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 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); } /** @@ -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)); } } diff --git a/src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java b/src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java index 6918336..9249d96 100644 --- a/src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java +++ b/src/main/java/org/sopt/makers/vo/slack/block/HeaderBlock.java @@ -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)); } } diff --git a/src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java b/src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java index 8042a01..c71b1b7 100644 --- a/src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java +++ b/src/main/java/org/sopt/makers/vo/slack/block/SectionBlock.java @@ -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 fields, diff --git a/src/main/java/org/sopt/makers/vo/slack/element/Button.java b/src/main/java/org/sopt/makers/vo/slack/element/Button.java index cfb0ea6..f65b272 100644 --- a/src/main/java/org/sopt/makers/vo/slack/element/Button.java +++ b/src/main/java/org/sopt/makers/vo/slack/element/Button.java @@ -1,8 +1,10 @@ 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, @@ -10,6 +12,6 @@ public record Button( 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); } } diff --git a/src/main/java/org/sopt/makers/vo/slack/text/MarkdownText.java b/src/main/java/org/sopt/makers/vo/slack/text/MarkdownText.java new file mode 100644 index 0000000..fd3b25f --- /dev/null +++ b/src/main/java/org/sopt/makers/vo/slack/text/MarkdownText.java @@ -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); + } +} diff --git a/src/main/java/org/sopt/makers/vo/slack/text/PlainText.java b/src/main/java/org/sopt/makers/vo/slack/text/PlainText.java new file mode 100644 index 0000000..c10f571 --- /dev/null +++ b/src/main/java/org/sopt/makers/vo/slack/text/PlainText.java @@ -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); + } +} diff --git a/src/main/java/org/sopt/makers/vo/slack/text/Text.java b/src/main/java/org/sopt/makers/vo/slack/text/Text.java index 9517229..1496b10 100644 --- a/src/main/java/org/sopt/makers/vo/slack/text/Text.java +++ b/src/main/java/org/sopt/makers/vo/slack/text/Text.java @@ -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(); }