먼저 slack에 로그인 한 뒤 워크스페이스를 만들어줍니다.
만들어준 워크스페이스를 열어줍니다.
채널 추가를 눌러서 채널을 만들어줍니다.
저는 #ecommerce와 #shop이라는 채널을 추가를 했습니다.
앱이 잘 생성이 되었다면 앱을 클릭해주고 Incoming Webhooks 메뉴에 들어갑니다.
들어간 다음 Add New Webhook to Workspace라는 버튼을 눌러 채널을 연동하고 웹훅을 만들어줍니다.
아까 만들어준 #ecommerce로 연동을 해줍니다.
web과 slack 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.slack.api:slack-api-client:1.27.2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
yml 설정 파일
notification:
slack:
webhook:
url: https://hooks.slack.com/services/[webhook URL]
아까 위에서 만들어준 webhook의 url을 넣어주면 설정이 완료가 됩니다.
Controller
@RestController
@RequiredArgsConstructor
public class HelloSlackClientController {
@Value("${notification.slack.webhook.url}")
private String slackAlertWebhookUrl;
private final ObjectMapper objectMapper;
@GetMapping(value = "/hello-error-slack-client")
public String helloErrorSlackClient() throws IOException {
Slack slack = Slack.getInstance();
String errorMessage = "주문에서 에러 메세지 발생!!!";
SlackErrorMessage slackErrorMessage = new SlackErrorMessage(errorMessage);
WebhookResponse response = slack.send(slackAlertWebhookUrl, objectMapper.writeValueAsString(slackErrorMessage));
return "Hello Slack Alert Sent = " + response.getCode();
}
}
메서드로 정의된 위와 같은 코드를 try-catch에서 catch 구문에 특정 시스템 오류가 발생 했을 때 실행 시키는 방식으로 많이 사용합니다.
필자의 회사는 에러에 대한 이슈가 많고 사용자들이 주로 퇴근 시간 이후에 접속을 한다.
또한 에러 모니터링이 존재하지 않았기에 에러가 일어나거나 시스템 다운이 되면 다음날 출근할 때까지 혹은 원격으로 퇴근하고나서 접속하지 않으면 알 수 없었다.
이러한 상황에서 Slack을 도입하여 에러 메세지를 받아서 바로 에러에 대응할 수 있게 도입을 해보았다.
@ControllerAdvice
@RequiredArgsConstructor
public class ControllerAdviser {
private final SlackApi slackApi;
@ExceptionHandler(Exception.class)
public void SlackErrorMessage(Exception e){
slackApi.sendErrorForSlack(e);
}
@ExceptionHandler(RequestNotFoundException.class)
public ResponseEntity<String> RequestNotFoundException(RequestNotFoundException e) {
int statusCode = e.getStatusCode();
return ResponseEntity.status(statusCode).body(e.getMessage());
}
@ExceptionHandler(GlobalException.class)
public ResponseEntity<String> GlobalException(GlobalException e) {
int statusCode = e.getStatusCode();
return ResponseEntity.status(statusCode).body(e.getMessage());
}
}
@RestController
public class SlackController {
@GetMapping(value = "/error/v1")
public String ErrorSlackClient1() {
throw new ReadOnlyBufferException();
}
@GetMapping(value = "/error/v2")
public String ErrorSlackClient2() {
throw new RequestNotFoundException("뀨잉");
}
}
@Service
@RequiredArgsConstructor
public class SlackService implements SlackApi {
@Value("${notification.slack.webhook.url}")
private String slackAlertWebhookUrl;
private final ObjectMapper objectMapper;
@Override
public String sendErrorForSlack(Exception exception) {
Slack slack = Slack.getInstance();
WebhookResponse response = null;
try {
ObjectMapper objectMapper = new ObjectMapper();
StringWriter writer = new StringWriter();
exception.printStackTrace(new PrintWriter(writer));
// TODO : 주석 처리 해둔 Map으로 아래와 같이 사용 가능~!
// Map<String, String> slackMessage = new HashMap<>();
// slackMessage.put("text", writer.toString());
SlackErrorMessage slackErrorMessage = new SlackErrorMessage(writer.toString());
response = slack.send(slackAlertWebhookUrl, objectMapper.writeValueAsString(slackErrorMessage));
return "Hello Slack Alert Sent = " + response.getCode();
} catch (IOException e) {
// TODO : 예외 처리
}
return null;
}
}
채널에 로그가 제대로 찍힌 것을 확인할 수 있다.
이와 같이 ControllerAdviser에서 handling을 해준 에러들은 메시지가 가지않습니다.
위 에러 메시지는 가독성이 좋지 않아 에러가 정확히 어떤 에러인지 또 언제 어디서 발생했는지 파악하기 매우 어렵습니다.
그래서 아래와 같이 코드를 수정했습니다.
@Override
public String sendErrorForSlack(Exception exception) {
Slack slack = Slack.getInstance();
WebhookResponse response = null;
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, String> slackMessage = new HashMap<>();
StringWriter writer = new StringWriter();
exception.printStackTrace(new PrintWriter(writer));
String emoji = "\u2620";
String errorTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String errorPath = request.getRequestURI().toString();
String exceptionName = exception.getClass().toString();
String exceptionRoot = exception.getStackTrace()[0].toString();
String message = String.format("%s [%s] - [My-Slack-Message]- [%s] - [%s] - [%s]", emoji, errorTime, errorPath, exceptionName, exceptionRoot);
slackMessage.put("text", message);
response = slack.send(slackAlertWebhookUrl, objectMapper.writeValueAsString(slackMessage));
return "Hello Slack Alert Sent = " + response.getCode();
} catch (IOException e) {
// TODO : 예외 처리
}
return null;
}
순서대로
- 아이콘
- 에러의 발생 시간
- 에러가 발생한 서버
- EndPoint
- Error명
- Error가 발생한 클래스와 상세 위치