ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 에러 로그 발생시 슬랙 알림 보내기
    공부방 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/슬랙에서-서버-에러-알림-받고-유연하게-에러-대응하기

    https://kim-jong-hyun.tistory.com/104

    '공부방' 카테고리의 다른 글

    Swagger 설정  (1) 2022.10.27
    🤔 JWT  (0) 2022.10.14

    댓글

Designed by Tistory.