-
에러 로그 발생시 슬랙 알림 보내기공부방 2022. 11. 23. 14:48
💬 Slack 설정
워크 스페이스 생성 및 채널 생성
새로운 워크스페이스를 만들거나 기존의 워크스페이스 사용
에러 로그를 받을 새로운 채널 생성
채널 생성 후 우클릭 → View channel details 를 클릭해 상세 정보 페이지로 이동
Webhooks 추가
Integrations 항목에 들어가 App 을 추가
- Github 앱을 통해서 commit 이나 pull request 등도 확인할 수 있다.
Webhook App을 추가
- 슬랙을 통해 알림을 받을 예정이므로 Incoming Webhooks 을 install
추가 해둔 채널에 Incoming WebHooks integration을 추가한다.
추가를 하면 WebHook URL 과 사용 방법에 대한 안내를 해준다.
Webhook 을 통한 알림 전송
- 웹훅을 통해 알림을 전송하는 방법은 2가지가 있다.
- POST 요청에 JSON 문자열을 payload 파라미터 형태로 전송
- POST 요청에 JSON 문자열을 body로 전송
test
curl -X POST --data-urlencode "payload={\\"channel\\": \\"#error-log\\", \\"username\\": \\"webhookbot\\", \\"text\\": \\"This is posted to #error-log and comes from a bot named webhookbot.\\", \\"icon_emoji\\": \\":ghost:\\"}" <https://hooks.slack.com/services/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>
- slack에 메시지 도착
🖥️ Spring boot 설정
Spring boot에서 에러 로그가 발생했을 때 웹훅으로 슬랙에 알림 메시지를 보내도록 설정
다른 사용자들이 사용하기 좋게 만든 프로젝트를 사용
에러 발생시 슬랙 알림
slack-webhook 을 사용
의존성 추가
implementation ("net.gpedro.integrations.slack:slack-webhook:1.4.0")
Config 설정
- SlackApi 를 빈으로 설정
@Configuration public classSlackLogAppenderConfig { @Value("${logging.slack.webhook.uri}") privateStringslackWebhookURI; @Value("${logging.slack.token}") privateStringtoken; @Bean publicSlackApi getSlackApi() { return newSlackApi(slackWebhookURI+token); } }
컨트롤러 어드바이스를 통해 특정 예외가 발생시 슬랙으로 알림을 오도록 설정
@ControllerAdvice public class ExceptionAdvice { private final SlackApi slackApi; @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleRuntimeException(HttpServletRequest req, Exception e) { ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR); sendSlackMessage(req, e); return ResponseEntity.internalServerError().body(response); } private void sendSlackMessage(HttpServletRequest req, Exception e) { SlackAttachment slackAttachment = new SlackAttachment(); slackAttachment.setFallback("Error"); slackAttachment.setColor("danger"); slackAttachment.setTitle("⚠️ ERROR DETECT"); slackAttachment.setTitleLink(req.getContextPath()); slackAttachment.setText(e.getStackTrace().toString()); slackAttachment.setColor("danger"); slackAttachment.setFields(getSlackField(req)); SlackMessage slackMessage = new SlackMessage(); slackMessage.setAttachments(Collections.singletonList(slackAttachment)); slackMessage.setIcon(":ghost:"); slackMessage.setText("⚠️ SERVER EXCEPTION DETECT"); slackMessage.setUsername("Beomsic"); slackApi.call(slackMessage); } private List getSlackField (HttpServletRequest req) { List<SlackField> fields = new ArrayList<>(); fields.add(new SlackField().setTitle("Request URL").setValue(req.getRequestURI().toString())); fields.add(new SlackField().setTitle("Request Method").setValue(req.getMethod())); fields.add(new SlackField().setTitle("Request Time").setValue(new Date().toString())); fields.add(new SlackField().setTitle("Request IP").setValue(req.getRemoteUser())); return fields; } }
test
@GetMapping("/slack") public void testSlack() { throw Exception("test"); }
에러 로그 발생시 슬랙 알림
logback-slack-appender 사용
의존성 추가
implementation 'com.github.maricn:logback-slack-appender:1.6.1'
logback.xml
<appender name="SLACK" class="com.github.maricn.logback.SlackAppender"> <webhookUri>${SLACK_WEBHOOK_URI}</webhookUri> <channel>#error-log</channel> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>${ERROR_LOG_PATTERN}</pattern> </layout> <username>${USER_NAME}</username> <iconEmoji>${EMOJI}</iconEmoji> <colorCoding>true</colorCoding> </appender> <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="SLACK" /> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> </appender>
실행 결과
📌 별도 스레드에서 처리하기
슬랙 웹 훅에 요청을 보내고 그 응답을 받는 시간을 비즈니스 로직이 기다리는 것은 비효율적이다.
따라서, 별도의 스레드에서 슬랙 알림 요청을 처리하도록 변경한다.
@GetMapping("/slackTest") public ResponseEntity<Void> slack() throws Exception { long startTime = System.nanoTime(); RestTemplate restTemplate = new RestTemplate(); for (int i = 0; i < 10; i++) { try { String result = restTemplate .getForObject("<http://localhost:8080/api/v1/users/slack>", String.class); } catch(Exception e) { } } long finishTime = System.nanoTime(); long elapsedTime = finishTime - startTime; System.out.println("startTime(ms) : " + startTime); System.out.println("finishTime(ms) : " + finishTime); System.out.println("elapsedTime(ms) : " + elapsedTime); return ResponseEntity.ok().build(); } @GetMapping("/slack") public void slackk() throws Exception{ throw new Exception(); }
- 컨트롤러에서 여러번 Exception을 발생 시키는 코드
반복문을 10번 실행했을 경우 발생 결과
15번 실행 결과
TaskExecutor Bean 등록
스프링이 제공하는 TaskExecutor을 Bean으로 등록
- ThreadPoolTaskExecutor 등록
@EnableAsync @Configuration public class AsyncConfig { private static int CORE_POOL_SIZE = 15; private static int MAX_POOL_SIZE = 25; private static int QUEUE_CAPACITY = 10; private static String THREAD_NAME_PREFIX = "async-task"; @Bean public TaskExecutor createTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(CORE_POOL_SIZE); executor.setMaxPoolSize(MAX_POOL_SIZE); executor.setQueueCapacity(QUEUE_CAPACITY); executor.setThreadNamePrefix(THREAD_NAME_PREFIX); executor.initialize(); return executor; } }
corePoolSize
- 동시에 실행시킬 스레드의 개수
maxPoolSize
- 스레드 풀의 최대사이즈를 지정
queueCapacity
- 큐의 사이즈를 지정
비동기 호출
private final SlackApi slackApi; private final TaskExecutor taskExecutor; public SlackUtils(SlackApi slackApi, TaskExecutor taskExecutor) { this.slackApi = slackApi; this.taskExecutor = taskExecutor; } public void sendSlackMessage(HttpServletRequest req, Exception e) { Runnable runnable = () -> { SlackAttachment slackAttachment = new SlackAttachment(); slackAttachment.setFallback("Error"); slackAttachment.setColor("danger"); slackAttachment.setTitle("⚠️ ERROR DETECT"); slackAttachment.setTitleLink(req.getContextPath()); slackAttachment.setText(e.getStackTrace().toString()); slackAttachment.setColor("danger"); slackAttachment.setFields(getSlackField(req)); SlackMessage slackMessage = new SlackMessage(); slackMessage.setAttachments(Collections.singletonList(slackAttachment)); slackMessage.setIcon(":ghost:"); slackMessage.setText("⚠️ SERVER EXCEPTION DETECT"); slackMessage.setUsername("Server Exception!!"); slackApi.call(slackMessage); }; taskExecutor.execute(runnable); }
빈으로 주입한 TaskExecutor를 통해 비동기 호출을 한다.
10번 실행 결과
15번 실행 결과
⇒ 걸리는 시간이 줄어들었다.
참고 자료
https://dundung.tistory.com/232
https://shanepark.tistory.com/430
https://github.com/gpedro/slack-webhook
https://github.com/maricn/logback-slack-appender
https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/
https://velog.io/@server30sopt/슬랙에서-서버-에러-알림-받고-유연하게-에러-대응하기
'공부방' 카테고리의 다른 글
Swagger 설정 (1) 2022.10.27 🤔 JWT (0) 2022.10.14