(Slack API, DRF) Django와 Slack API를 연동하여 에러 처리 로직 / 에러 로깅 봇 만들기
Slack은 참 다양한, 많은 분야에서 이용된다. 회사의 사내 메신저, 협업 툴, 마케팅 도구, 혹은 백오피스 등 무궁무진한 쓰임새가 있다.
이러한 유저들의 니즈에 걸맞게, Slack에서는 상당히 많은 종류의 통합을 제공한다.
나도 회사 업무를 하며 여러 번의 통합을 진행했고, 그 부분들을 까먹지 않고자 포스팅해보려고 한다.
내가 진행했던 부분을 크게 두 가지로 나눠보면 다음과 같다.
- Slack API를 활용한 Django 에러 로깅 봇 생성
- Github Webhook을 활용한 Commit/Push/PR/Merge 로깅
오늘은 먼저 1번인 Slack API와 에러 로깅에 대해 말해보려고 한다.
1. Slack API
먼저 Slack API는, 이름에서 알 수 있다시피 Slack의 여러 가지 기능들을 API Call을 통해 손쉽게 활용할 수 있는 기능이다.
API Call까지의 방법은 대략적으로 다음과 같다.
- Slack으로부터 Access Token (OAuth)를 발급받는다.
- Access Token을 통해 인증을 진행한다.
- 사용하고자 하는 기능에 맞게 HTTP GET/POST 를 보낸다. (위의 docs 참고)
- 응답을 받은 후 적절히 처리한다.
1부터 진행해 보자. 먼저 나는 1의 과정과 메시지 전송을 대신해 주는 봇을 만들고 시작할 예정이다.
통합하고자 하는 워크스페이스에서 봇을 만들어 보자. apps 링크
- Create New App을 누르고,
- 적당히 이름을 (ex: notiBot? 이라던지..) 지어준 후
- features and functionality를 Bots으로 설정한다.
- 마지막으로 Scope를 적절히 설정하면 된다.
-> 단순 메시지 전송만 진행하려고 하므로 chat:write만을 설정해 준다.
install to workspace를 진행하면 Access Token이 발급되는데, 중요한 토큰이니 잠시 메모장 같은 곳에 저장해 두자..
이후 앱에 봇을 추가해주자
여기까지 성공적으로 진행했다면, 이제 형식에 맞게 request body를 만들어 보자.
2. Django 에서의 설정
먼저 나는 Django Rest Framework를 활용해 API개발을 진행하고 있다. 정말 편하게도 Slack API는 파이썬의 패키지 관리자인 pip으로 손쉽게 설치할 수 있다.
python 가상 환경을 실행하고 다음과 같은 명령어를 입력하자.
pip install slackclient
어쩌고 저쩌고 Success~가 뜨고 설치가 완료되면, 이후 이를 활용하고자 하는 곳에서 아래와 같이 모듈을 import해 주자.
from slack import WebClient
이후 WebClient에서 요구하는 대로 body를 작성해 주자.
나의 경우엔 celery를 통한 비동기 처리를 추가했지만, 이 부분은 본인이 사용하는 구조에 맞게 건너뛰거나 하자.
대략적으로 post하는 부분을 짜보자면 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | from slack import WebClient from somewhere_app_exists import app from somewhere_secret_exists import slack_api_access_token, slack_post_channel_url @app.task(ignore_result=True) def slack_error_post(**kwargs): client = WebClient(token=slack_api_token) body_raw = {from_somewhere} try: client.chat_postMessage( channel = slack_post_channel_url, blocks = json.dumps(body_raw) ) except Exception: pass | cs |
- 3번째 줄의 app은 Celery를 사용할 경우 그 위치이고, 7번째 줄은 celery task에 넣어주는 명령어이다.
-> celery를 사용하지 않는다면 둘 다 삭제해버려도 무방할 듯 하다. - 4번째 줄의 slack_api_access_token은 위에서 발급해 둔 Access Token을 str로 설정해 둔 부분이고,
- 같은 줄의 slack_post_channel은 post를 보낼 채널의 url 주소이다.
대략적으로 코드의 프로세스를 보자면 아래와 같다.
- 기본적으로 9번째 줄의 client = WebClient(token = {Access Token})을 통해 WebClient 객체를 생성 후 initialize 해 주고,
- 10번째 줄의 body_raw처럼 blocks 안을 본인의 입맞에 맞게 커스터마이징 한 후,
- client.chat_postMessage()로 해당 채널의 url과 body를 함께 post해주는 코드이다.
정답이 아닐 수도 있지만, 나는 이렇게 썼다(…)
blocks를 만드는 부분은 Slack 답게 야무지게 커스터마이징이 가능하다. (slack에서 제공하는 Block Kit Builder)
위의 툴로 본인만의 block을 야무지게 꾸며 보자.
- 참고로 이모지를 활용하려면 아래와 같이 “emoji”: True를 넣어주어야 하고, :(이모지 이름): 와 같이 써야한다.
(개발자특 : 이모티콘 이런거 좋아함)
1 2 3 4 5 6 7 8 | { "type": "header", "text": { "type": "plain_text", "text": f"Error 발생! code : {code} :glitch_crab:", "emoji": True } }, | cs |
여기까지 정상적으로 진행했다면, API call 혹은 Postman등으로 post를 한번 보내 보자.
아직 에러 로깅과 관련된 부분을 넣진 않았으므로, 쓸쓸한 Plain Text만이 우리를 반겨준다. (나는 안해봤으므로 사진은 따로 없음)
따라서 이제 django의 exception handler와 본인만의 에러처리 로직을 커스터마이징해 원하는 에러들을 Slack으로 전달할 수 있게끔 만들어 보자.
3. Exception Handling
사실 에러 처리는 방법이 사람마다 다르고, 매우 다양하다.
- DRF에서 기본적으로 제공하는 Http404Response 등을 활용하거나,
- 단순 HTTP status code와 detail만을 Return할 수도 있고,
- 에러코드 등을 직접 정의해 문서를 만든 후 이를 통해 프론트와 소통하는 방법 등
회사에 맞게, 개발자에 맞게 너무 다양한 방법이 있다.
나의 경우엔 3~4자리의 정수를 코드로 정의해 이에 맞게 20x 40x를 JSONResponse로 Return해 주는데,
오늘의 경우엔 데이터 무결성 이상이나 토큰 변조, 서버 에러 등 (401, 403, 50x 등?) 따로 모니터링이 필요한 부분들만 모아 Slack으로 보내보려고 한다.
(사실 이미 AWS CloudWatch를 통해 이런 에러들을 로깅하고 있지만, Slack은 핸드폰에 알림이 도착하므로, 그 용도로 사용하려고 한다)
각설하고, 적당한 위치에 custom exception handler를 생성해 주자.
Django의 기본 exception handler를 적당히 커스텀해, 아래와 같은 코드를 작성했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from rest_framework.views import exception_handler from rest_framework import status from {somewhere_error_response_exists} import failure_response from {somewhere_error_code_exists} import unhandled_error_code def custom_exception_handler(exc, request): response = exception_handler(exc, request) if response is not None: if status.is_server_error(response.status_code) == True: return failure_response(request, unhandled_error_code, exc) return response return failure_response(request, unhandled_error_code, exc) | cs |
다른 부분은 큰 의미는 없고,
- 11번째 줄을 통해 HTTP 50x를 판별해 따로 정의해 둔 unhandled_error_code를 담아 넘겨주고,
- 내가 직접 raise한 에러가 아니면 15번째 줄을 통해 역시 unhandled_error_code를 담아 보내준다.
12, 15번째 줄과 같이 모든 에러 처리에 request와 exc를 함께 담아 보내주면, 위의 chatMessage에서 만들어 준 block를 좀 더 정보가 가득하게 꾸며줄 수 있다!
그럼 이제 에러를 response하는 부분을 들여다보자.
4. Error Response, Logging
각자의 에러 처리 함수를 들여다보자. 현재 내가 사용중인 에러 처리 함수를 (매우) 간략화하면 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | from {somewhere_error_code_exists} import unhandled_error_code from {somewhere_def_exists} import slack_error_post def failure_response(request, code, exc=None): if code == unhandled_error_code: slack_error_post.apply_async(\ queue="your_queue", kwargs={\ 'message': "your_message" 'exc': f'{exc}'} ) return JsonResponse({"message": "blahblah"}, status=status_code) | cs |
내가 활용할 부분은 다음과 같다.
- exception handler에서 함수를 호출하며 같이 담아준 request와 exc를 받아, 가공해 준다.
- 여러 절차를 통해 5~11번째 줄과 같이 kwargs에 에러 정보를 담아 비동기로 Slack에 post 요청을 보낸다.
- 이후 적당히 유저에게 보여줄 response를 생성 후 Return 한다.
이후 처음에 만들어 준 slack_error_post의 body에서 request.path_info나 exc.detail등 여러 정보를 받아, block을 생성해 준다.
5. 결과물
나는 이모티콘과 request, exc 정보를 활용해 block을 꾸몄고, error가 post되면 아래와 같이 메시지가 도착한다.
(꽂게가 기어다니고, 사이렌이 울리고, 유령이 놀리고, 돼지가 걸어가고,,,,)
실제로 발생한 에러 내용이다보니 지워진 내용이 더 많지만,
위와 같이 어떤 유저가 어떤 경로에서, 어떤 에러를 일으켰는지를 알 수 있다.
이렇게 구현한 에러 처리 방법과 로깅 방식이 정답은 아닐 수 있지만, 나는 위처럼 구현하고 커스터마이징해 주며 아직까지 잘 활용하고 있다!
끝. 남은 어린이날… 놀러가야징
댓글남기기