슬랙에서 서버 에러 알림 받고 유연하게 에러 대응하기

Server The SOPT·2022년 7월 17일
1
post-thumbnail

✏️ 작성자: 김혜정
📌 작성자의 한마디: 이거 하면 나 서버 에러 모니터링해서 대응해봤다 깔짝 대기 가능

해당 글에서는 Nodejs 환경에서 슬랙 알림을 전송하는 과정에 대해서 다룹니다.

그렇다면 이게 왜 필요할까요?

사실 이건 개발하는데에 있어 꼭 필요한 부분은 아니지만, 이후 유지보수 시에 편리하게 사용될 수 있습니다!

ec2에 우리의 서버를 올려두면 매일 들어가서 로그를 확인하거나 클라 분들께서 엇?! 서버 에러 났는데요?! 해야 확인이 가능한데

아주 크리티컬한 서버 에러의 경우는 슬랙 알림을 추가한다면?
바로바로 슬랙을 보고 서버 에러가 났네? 하면서 코드를 수정할 수 있겠죠?

저는 아주 유용했어서 우리 섭섭이들에게 추천드릴까 글을 적어봅니다.

슬랙이 있어야만 설정이 가능하다는 점 .....

슬랙 웹훅(Webhook) 추가

서버 에러를 받을 채널 추가하기

  1. 서버 에러를 받을 팀슬랙에 새 채널을 추가해줍니다.

  2. 앱추가에 들어가서 incomming webhooks을 추가해줍니다.
    안뜨면 web이라고만 쳐도 바로 뜬답니다!

추가 버튼을 누르면 다음과 같은 화면이 뜹니다.

여기서 Slack에 추가버튼을 누릅니다.

그 다음 우리가 추가한 채널을 선택해준 후 수신 웹후트 통합 앱추가를 눌러줍니다.

  1. 아래 화면이 나오면 어떤 식으로 슬랙 메세지를 보내야 하는지, 웹 후크 URL 정보 등이 나옵니다.

웹 후크 URL은 메모장에 적어두세요!

저의 경우, 웹훅 이름과 아이콘을 넣어주었습니다!
(안해도 됩니다. 근데 그러면 기본이라 살짝 못생겼어요.)

이후 설정 저장을 눌러주면 끝

프로젝트에 슬랙 알림 적용하기

라이브러리 추가

슬랙에게 POST 요청으로 알림 내용을 보내줘야해서 axios 라이브러리를 설치해줍니다.

yarn add axios

.env에 웹훅 url 추가하기

config 파일에 바인딩 시켜주기

저희는 env에 내용들을 config/index.ts에 바인딩 해주니까 이렇게 추가해줍니다!

slack 에러 메세지를 만들어 줄 함수 만들기

슬랙 알림을 한 두곳에 찍으면 괜찮지만, 여러 군데에서 사용될 수 있잖아요?!
그래서 따로 빼서 만들어줍니다.
저의 경우, middleware/slackAlarm.ts 를 만들어주었는데 이 위치가 아니라면 댓글로 정정 부탁드립니다. ㅎㅎ ...

제가 슬랙 알림 메세지로 보낼 형태는 이런 식입니다!
House-Server와 아이콘은 아까 위에서 설정해주었으니 그 밑에 들어가는 부분을 custom 해보겠습니다.

Hous 서버 에러 === title
서버 내부 오류입니다. === text
Error Stack: === fields.text
Error Stack 로그 === fields.text
From API Server [prodution] === footer
왼쪽에 띠 === color

  • 전체 코드
import axios from 'axios';
import config from '../config';

const API_TOKEN = config.slackAlarm; //env에 달았던 웹 훅 url

// 슬랙 메세지 왼쪽 띠 색상
const colors = {
  primary: '#007bff',
  info: '#17a2b8',
  success: '#28a745',
  warning: '#ffc107',
  danger: '#dc3545'
};

// 슬랙 에러 스택 보여주고 싶을 경우, 마크다운 안에 넣어 이쁘게 보여주려고 사용
export interface SlackMrkdwnFormat {
  title: string;
  value: string;
}

// 슬랙 메세지 담을 형태
export interface SlackMessageFormat {
  color: string; // 띠 컬러
  title: string; // Hous 서버 에러
  text: string; 
  fields?: SlackMrkdwnFormat[];
  footer?: string; // From API Server [production]
}

// mrkdown 
export interface SlackMessage {
  mrkdwn: boolean;
  text: string;
  attachments: SlackMessageFormat[];
}

const getChannels = () => {
  return {
    production: API_TOKEN
  };
};

// 슬랙 알림 메세지 전송하는 함수
const sendMessage = async (message: SlackMessageFormat) => {
  if (!message) {
    console.log('메시지 포멧이 없습니다.');
    return;
  }

  // 보내줄 메세지 형태 작성
  const data: SlackMessage = {
    mrkdwn: true,
    text: '',
    attachments: []
  };

  if (!message.title && !message.text) {
    console.log('메시지 내용이 없습니다.');
    return;
  }

  message.footer = `From API Server [${config.env}]`;
  data.attachments.push(message);

  // 슬랙에 전송
  axios({
    url: getChannels().production,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    data
  });
};

export default { sendMessage, colors };

그리고 500 에러를 보내주는 부분에 슬랙 메세지를 전송하는 함수를 호출해줍니다.
저희 하우스팀은 에러 핸들링을 해주고 있어서 500 에러를 보낼 때 전송하도록 다음과 같이 추가해줍니다.

만약 세미나 코드를 사용 중이시라면, controller에서 catch() 부분에 추가해주면 되겠네요!

const message: SlackMessageFormat = {
   color: slackAlarm.colors.danger,
   title: 'Hous 서버 에러',
   text: err.message,
   fields: [
      {
         title: 'Error Stack:',
         value: `\`\`\`${err.stack}\`\`\`` //여기서 ```를 추가해서 마크다운 형태로 보내줍니다.
      }
   ]
};
slackAlarm.sendMessage(message); //슬랙에게 알림 전송
  • 전체코드
import { ErrorRequestHandler, NextFunction, Request, Response } from 'express';
import config from '../config/index';
import { logger } from '../config/logger';
import slackAlarm, { SlackMessageFormat } from '../middleware/slackAlarm';
import messages from '../modules/responseMessage';
import util from '../modules/util';
import { ErrorWithStatusCode } from './errorGenerator';

const generalErrorHandler: ErrorRequestHandler = (
  err: ErrorWithStatusCode,
  req: Request,
  res: Response,
  next: NextFunction
): void | Response => {
  const { message, statusCode } = err;
  logger.error(err.stack); //에러 스택까지 보여줌 (개발 단계에선 보기 편하니까!)

  // 인자로 statusCode를 넘기지 않는 경우, 500 에러를 보냄
  if (!statusCode || statusCode == 500) {
    // 프로덕트 환경일 때, 500 에러 발생 시 슬랙 알림 울리도록 추가
    if (config.env === 'production') {
      const message: SlackMessageFormat = {
        color: slackAlarm.colors.danger,
        title: 'Hous 서버 에러',
        text: err.message,
        fields: [
          {
            title: 'Error Stack:',
            value: `\`\`\`${err.stack}\`\`\``
          }
        ]
      };
      slackAlarm.sendMessage(message);
      logger.error(`[statusCode: ${err.statusCode}] message: ${err.message}`);
    }
    return res.status(500).send(util.fail(500, messages.INTERNAL_SERVER_ERROR));
  } else {
    return res.status(statusCode).send(util.fail(statusCode, message));
  }
};

export default generalErrorHandler;

테스트를 해볼까요? 🔥

저는 컨트롤러에서 임의로 throw 로 에러를 발생시켜서 알림이 왔나 확인해보았습니다.

/**
 *  @route POST /auth/signup
 *  @desc signup Create User
 *  @access Public
 */
const signup = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void | Response> => {
  const errors: Result<ValidationError> = validationResult(req);
  if (!errors.isEmpty()) {
    return res
      .status(statusCode.BAD_REQUEST)
      .send(
        util.fail(statusCode.BAD_REQUEST, message.BAD_REQUEST, errors.array())
      );
  }

  const signupDto: SignupDto = req.body;
  try {
    const data: PostBaseResponseDto = await UserService.createUser(signupDto);

    const accessToken: string = getToken(data._id);

    throw errorGenerator({
      msg: "서버 내부 에러입니다.",
      statusCode: statusCode.INTERNAL_SERVER_ERROR
    });
    // 아래는 throw 되어서 실행 안됨
    return res
      .status(statusCode.CREATED)
      .send(
        util.success(statusCode.CREATED, message.SIGNUP_SUCCESS, accessToken)
      );
  } catch (error) {
    next(error);
  }
};

이렇게 에러를 던지고 Api 테스트를 진행 후 아래와 같이 슬랙 알림이 온다면?! 성공!!!!!

저희 팀은 에러 핸들링 관련 부분은 서팟장님 잡채 블로그를 참고하여 작성하였습니다. (최고)

에러 핸들링을 만들어주면 로그를 찍을 때나 이렇게 슬랙 알림 보내는게 간편해지니까 블로그 한 번쯤은 보면 좋을 것 같아요!

profile
대학생연합 IT벤처창업 동아리 SOPT 30기 SERVER 파트 기술 블로그입니다.

0개의 댓글