매일 오전 4시에 backupdb.sh를 실행하고, 이를 db_backupdb.log에 로깅한다는 간단한 내용이다.
이렇게 작성하고 자고 일어나보니 아무 일도 없었다... 해결하면서 여러 가지 문제들을 겪었는데, 여기서 겪었던 크론탭을 사용하면서 겪었던 관련 문제점들과 해결책을 소개한다.
0. 크론탭 로그 확인
일단 당연히 문제가 일어났으면 기본적으로 로그를 확인해야 한다. ubuntu 18.04 기준으로 크론 관련 로깅은 기본적으로 /var/log/syslog에 기록되니 여기를 뒤져보면 되는데, 문제는 여긴 시스템 관련 로깅들이 다 같이 작성되는 곳이다 보니 AWS EC2에선 워낙 별별 로그가 많다. 그래서 아래의 명령어로 뒤져보면 크론 관련 메세지만 나와서 보기 편하다. 여기서부터 원인을 찾기 쉬울 것이다.
cat /var/log/syslog | grep CRON
1. (CRON) info (No MTA installed, discarding output)
여기서 말하는 MTA는 Mail Transfer Agent로 메일 에이전트이다. 메일 에이전트가 없다니까 그냥 메일 에이전트를 깔면 해결되고, sudo apt-get install postfix 한 방이면 이 오류는 사라질 것이다. 문제는 이 오류가 왜 생기냐는 건데, 바로 내가 추가했던 로그 파일 때문이다.
크론 데몬의 로깅은 다른 데몬들처럼 로그 파일을 /var/log 이런데 작성하는 게 아니라, 메일 에이전트를 통해서 해당 크론탭을 소유한 각 유저에게 로그 내용이 전송된다. 나는 이 내용을 파일에 기록하도록 했으니 그 내용이 파일로 작성되어서, 다른 로깅들처럼 파일로 작성되는 것처럼 보이는 것이다. 왜 이런 식으로 처리되냐면, 리눅스는 여러 명의 유저들이 동시에 사용하는 것을 전제로 계정 시스템이 구현되어 있고, 크론탭 역시 각 유저별로 따로 작성되기 때문에 각 유저별로 따로 로그를 관리할 필요가 있기 때문이다. 만약 /var/log 아래에 로그가 한꺼번에 기록된다면, 루트 계정의 크론 로그는 일반 계정이 보면 안되니 읽기 권한에 제한을 줘야 하는 일이 발생한다. 그러면 당연히 일반 유저들은 자신의 로그를 확인할 수 없는 모순이 생긴다.
로그 파일을 열면 이렇게 뜨기 때문이다. 나는 cat 명령어로 이 파일을 여는데 문제가 생긴 줄 알았다. 그래서 혹시 로그 생성중에 뭔가 문제가 생겨서 깨진 파일이 생성됐나 확인해 보려고 크론 관련 로그를 뒤지고, 기본 쉘이 /bin/bash에서 /bin/sh로 바뀌었나 점검해 봤는데 아니었고 그냥 로그 파일이 저래 나온 거였다. vim db_backupdb.log로 열어보면 정상적으로 열리고 내용도 저거 한줄이다. ㅡㅡ;
아무튼 이 오류는 왜 생기냐면 쉘 스크립트를 실행하기 위해 쓴 명령어 source가 문제였다. 크론탭에서 사용하는 기본 쉘이 /bin/sh인데, 이 쉘은 /bin/bash와 달리 source 명령어를 지원하지 않는다. 그래서 source 명령어를 찾지 못해서 스크립트를 실행조차 못하고 끝났던 것이다. 그래서 1. 크론탭에서 사용하는 쉘을 바꾸거나 2. /bin/sh에서 사용할 수 있는 명령어를 사용하면 해결된다. 즉, 아래의 두 가지 방법중 하나만 하면 된다.
파이썬에서 패키지 의존성을 공유할 때 가장 범용적으로 사용되는 게 requirements.txt일 것이다. 현재 파이썬 환경에서 설치된 패키지들을 정리할땐 아래의 명령어를 사용한다.
pip freeze > requirements.txt
이 명령어를 통해 설치된 패키지들이 requirements.txt에 나열된다. 이 파일을 이용하여 패키지들을 설치하고자 할 때는, 다음과 같은 명령어를 사용한다.
pip install -r requirements.txt
보통은 이 두가지 명령어 정도면 프로젝트를 관리하는 데는 지장이 없을 것이다. 하지만, 상황별로 패키지를 다르게 관리해야 할 때도 있을 것이다. 나의 경우엔, 윈도우즈 PC에서 개발을 하고 우분투 기반의 서버에서 배포를 하는데, 서버에서 위 명령어를 이용하여 패키지들을 설치할 때 오류가 생겼었다. 그래서 오류가 나는 패키지를 찾아봤는데, pywin32라는 라이브러리였고, 이 라이브러리는 파이썬에서 윈도우즈 API들을 사용할 때 쓰는 라이브러리라 당연히 에러가 날 수 밖에 없었다. 그래서, 상황에 의존성을 따로 관리해야 할 필요성을 느꼈다.
물론 일회성이고, 문제가 되는 패키지가 한두개라면 그냥 복붙으로 파일을 새로 만들고 위처럼 해도 된다. 하지만 관리해야 할 패키지가 수십 개라면 저 방법은 너무 귀찮고 헷갈릴 것이다. 그래서 조금 더 우아한 방법이 있다.
2. 조건에 따른 패키지 설치
# requirements.txt
Django==3.0.8
django-import-export==2.3.0
django-sass-processor==0.8
django-suit==2.0a1
et-xmlfile==1.0.1
Flask==1.1.2
Flask-BasicAuth==0.2.0
pycparser==2.20
pytz==2020.1
pywin32==300; sys_platform == 'win32' - 문제가 되는 라이브러리
조건을 붙이길 원하는 패키지의 뒤에 ;를 붙이고, 조건을 넣는다. 나는 윈도우즈 플랫폼에서만 해당 패키지를 설치하길 원해서 윈도우즈 플랫폼에서만 해당 라이브러리를 설치하도록 설정하였다. 만약 리눅스에서만 설치하기 원하는 패키지라면 sys_platform == 'linux' (python2는 linux2)로 설정하면 된다.
위 내용에 대한 정보는 여기를 참조하면 된다. 해당 문서에서 소개하는 주요 마커들은 다음과 같다.
예를 들어, 파이썬의 버젼(2.7, 3.5, 3.6, ...)에 따라서도 다르게 설정하길 원한다면 python_version == "원하는 버젼"을 붙이면 될 것이다.
3. 파일을 아예 분리하기
위에 제시된 마커들로는 구분할 수 없는 정보가 있거나, 분리해야 하는 패키지가 너무 많다면 아예 파일을 나눠버릴 수 있다.
이런 식으로, 다른 파일을 읽어오도록 설정할 수 있다. 예를 들어, 배포 환경에선 `pip install -r requirements-production.txt` 명령어를 실행한다. 그러면 우선 requirements.txt를 읽어와서 그 안의 패키지를 설치하고, 그 다음엔 requirements-production.txt 에 적혀 있는 라이브러리들을 설치하게 된다. gunicorn, uwsgi와 같은 배포에만 필요한 라이브러리들이 여기에 들어가면 될 것이다.
관리해야 하는 패키지들이 간단하다면 역시 파일 하나로 모든 게 가능한 2번 방법, 조금 구조가 복잡하다면 3번 방법이 가장 적절할 것이다. 자세한 정보는 여기서 확인할 수 있다.
Mocha와 Should.js를 이용해 테스트 코드를 작성하던 중, 콜백 함수 내에선 assert 기능이 정상적으로 작동하지 않는 것을 발견했다. 처음엔 Should.js 자체의 문제인 줄 알고 assert 라이브러리로 바꿔서 시도했지만 똑같은 현상이 발생했다. 아래와 같이 명백히 오류가 나야 하는 부분도 문제 없이 통과가 되었다.
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
분명 나는 done()을 넣어 놨는데, 테스트 모듈에선 못 찾고 있었다. 그래서 다시 찾아본 결과, (0).should.be.equal(1)의 assert 문에서 이미 Error가 던져져서 코드 실행이 중단되고, 따라서 done()까지 코드가 가지 못하고 있는 상태였다. Promise를 이용한 비동기 함수라면 .catch로 해결하면 될 것이고, 콜백 함수를 이용한 비동기 문일 경우 다음과 같이 try ~ catch 문으로 테스트 함수를 감싸면 된다. 최종적으로 아래와 같다.
it('현재 온습도 출력 테스트', (done) => {
tempAndHum.getNowTah((data) => { // 테스트를 원하는 함수try {
// 원하는 테스트 코드 입력
done()
}
catch (err){
done(err)
}
})
})
Express.js를 이용하여 개발을 하면서 ORM 라이브러리로 Sequelize를 사용하고 있는데, 내 예상과 다르게 작동하는 부분이 많아서 조금 애를 먹고 있었다. 그 중에서 특히 내가 가장 삽질했던 부분을 공유하고자 한다.
Sequelize 모델 불러오기
우선 가장 먼저 ORM을 이용하려면 내 코드에 해당 모델을 불러와야 하는데, 대부분의 문서나 글에선 이미 모델을 불러온 걸 전제로 CRUD를 설명하는데, 이 모델을 가져오는 부분에서부터 막혔었다.
Sequelize-cli를 이용하여 다음 명령어를 이용하여 기본적인 파일을 생성했다.
sequelize init
이렇게 하면, models/index.js에서 실제 DB와 시퀄라이저가 연결된다. 그런데, index.js에는 모델을 불러오는 형식이 이미 지정되어 있고, 따라서 각 모델 파일들의 형식도 다음과 같이 지정되어 있다. 물론, 이러한 형태에서는 require(user.js) 형태로 불러와도 당연히 해당 모델을 쓸 수 없다. 그렇다고 다시 싹 다 Sequelize 모듈을 임포트 하기엔 지저분하고 비효율적이다. 분명 이렇게 쓰라고 만든 기능이 아닐 것이다...
"각 모델을 불러옴"이라고 주석을 넣은 부분의 코드를 읽어 보면, 현재 index.js가 있는 폴더 내의 파일을 모두 읽고(자기 자신 제외), 이를 전부 연동(associate)하여 db라는 객체 안에 넣는다. 그리고 이 db를 익스포트한다. 그렇다면, 당연히 익스포트된 db 객체만 가져와도 DB의 모든 기능을 이용할 수 있을 것이다.
따라서, 다른 파일에서 모델을 사용한다면...
const db = require('../models')
const tah = db["TempAndHum"] // 내가 만든 DB 이름
이런 식으로 DB 이름을 이용하여 불러올 수 있다. models/index.js 안에서 DBMS 연결과 모델 연결을 모두 해 주기 때문에, 다른 데에선 그냥 DB 이름만 갖고 불러오면 된다. DB 이름은,
나는 특정 테이블을 생성 시간순으로 정렬하여, 거기서 특정 몇 개의 항만 삭제하는 함수를 구현하고자 했다. 일반적인 SQL 문에서는 서브 쿼리를 이용하여 구현할 수 있는데, 시퀄라이저에서는 서브쿼리를 다루는 방법이 좀 복잡하고 직관적이지 않았다. 그래서 그냥 직접 SQL 쿼리를 박아넣었다.
const db = require('../models')
const tah = db["TempAndHum"]
tah.destroy({
where: {
id: [Sequelize.literal(`SELECT * FROM (SELECT id FROM tempandhum ORDER BY createdAt DESC LIMIT 3) AS tmp`)]
}
})
Sequelize.literal 함수를 통해 썡 SQL 쿼리를 넣었고, 내가 원하는 조건의 id들을 추출한 뒤 where 조건문에 넣어 조건에 따른 삭제를 수행한다. 쿼리를 SELECT * FROM ~~ AS tmp와 같이 래핑했는데, mariaDB에서는 서브쿼리에 limit가 적용이 안되서 위와 같이 편법으로 우회했다. 위 쿼리가 실제도 수행될 땐 다음과 같이 수행된다.
DELETEFROM `TempAndHum` WHERE `id` IN
(SELECT*FROM (SELECT id FROM tempandhum ORDERBY createdAt DESC LIMIT 3) AS tmp)
Django에서 아임포트를 이용한 결제 시스템을 만들던 중, 문제가 있었다. 결제를 완료한 후, 결제 정보를 POST method를 통해 서버로 보내야 하는데, 서버로 보내도 항상 받은 데이터가 empty로 표시가 되었다. 처음에는 프론트엔드 쪽에서 문제가 있는줄 알았는데, 알고 보니 Django의 view에서 POST를 받을 때 모든 데이터를 받을 수 있는 게 아니었다.
A dictionary-like object containing all given HTTP POST parameters, providing that the request contains form data. See the QueryDict documentation below. If you need to access raw or non-form data posted in the request, access this through the HttpRequest.body attribute instead.
django에는 기본적으로 email 발송 기능이 내장되어 있다. 그런데, 인터넷에 관련 예제를 찾아 보면 전부 gmail 발송이고, daum 메일(hanmail.net / daum.net) 발송에 대한 예제는 거의 없어서 정리하게 되었다.
이미 gmail로 발송을 해 봤다면 기본적인 맥락은 거의 유사하다.
1. Daum 메일 설정
daum 메일 -> 환경설정에 들어가자.
웹서버를 통한 메일 발송은 SMTP라는 프로토콜을 사용하게 되는데, 그러려면 다음과 같이 사용 설정을 해야 한다. 기본값으로 '사용 안 함'으로 되어있으니, 꼭 잊지 말고 바꿔야 한다.
2. django의 settings.py 변경
EMAIL_HOST = 'smtp.daum.net'# 메일 호스트 서버
EMAIL_PORT = 465# 서버 포트
EMAIL_HOST_USER = '사용할 이메일'
EMAIL_HOST_PASSWORD = '이메일 비밀번호'
EMAIL_USE_SSL = True
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 기본 발신자
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
SMTP 설정 페이지 바로 밑에 나온 안내문이다. 이대로 설정하되, 이미 gmail로 구현해 본 적이 있다면 다른 점이 몇개 있다.
Port : gmail은 587 포트를 사용하는데 여기서는 465 포트를 사용해야 한다.
EMAIL_USE_SSL : 내가 이 글을 쓰게 된 이유이다. 처음에 gmail을 쓰다가 다음 메일로 교체했는데, 어떤 오류도 없이 그냥 이메일 발송 페이지에서 웹서버가 멈춰 버리는 일이 있었다. 값을 쭉 점검해본 결과, 이 설정값에 문제가 있었다. 이전엔 EMAIL_USE_TLS = True 를 설정값으로 이용했다. TLS와 SSL은 유사하지만 엄연히 다른 프로토콜이었고, 이 값을 EMAIL_USE_SSL로 바꾸니까 정상적으로 작동되었다. 참고로, EMAIL_USE_SSL 과 EMAIL_USE_TLS를 동시에 넣으면 하나만 쓰라고 에러가 난다. 이 부분 반드시 체크하자.
이런 식으로 매번 form class나 템플릿을 만지며 라벨을 일일이 달아 주는 건 비효율적일 것이다. 그래서 직접 form의 label을 수정하는 것보다, 가능하다면 위와 같이 verbose_name을 적용하는게 제일 좋다.
코드를 수정하자.
yourapp/forms.py
from django.contrib.auth.forms import AuthenticationForm
classCustomAuthenticationForm(AuthenticationForm):
error_messages = {
'invalid_login': (
"비밀번호나 이메일이 올바르지 않습니다. 다시 확인해 주세요."
),
'inactive': ("이 계정은 인증되지 않았습니다. 인증을 먼저 진행해 주세요."),
}
def__init__(self, request=None, *args, **kwargs):super(CustomAuthenticationForm, self).__init__(*args, **kwargs) # 꼭 있어야 한다!
self.fields['password'].label = '비밀번호'
폼은 완성되었다. 이제 뷰를 수정하자.
yourapp/views.py
from django.contrib.auth import views as auth_view
from .forms import CustomAuthenticationForm
classCustomLoginView(auth_view.LoginView):
form_class = CustomAuthenticationForm
인턴으로 다니던 회사에서 데이터를 주기적으로 크롤링하고, 크롤링한 자료를 이용자들에게 제공해주는 웹 서버를 개발했다. 이 서버를 운영해야 하는데, 이 모든 작업이 정상 작동하는지 주기적으로 확인을 해야 한다. 서버는 사실 바로 안다. 그냥 주소를 입력했는데 안 들어가지면 문제가 있다. 내가 확인하기도 전에 아마 사용자 측에서 뭔가 피드백이 들어올 것이다.
하지만, 크롤링이 잘못되었을 경우엔 이 크롤링이 잘못되었다는 사실을 깨닫는 건 상대적으로 까다롭다. 그냥 데이터가 몇 개 비거나, 혹은 아예 안 보일 뿐이지 다른 모든 건 정상작동 할 것이다. (서버와 크롤링 모듈을 합쳐놓지 않은 이상) 물론, 서버 로그를 확인하는 방법도 있다. 하지만 AWS를 사용하는 지금으로선 putty를 켜고 들어가는 거 자체도 너무 귀찮았다. 그래서 그냥 이 과정을 자동화 시켜서, 나는 그냥 앉아서 정상 작동 여부를 알도록 했다.
해당 크롤링이 성공이든, 실패든 일단 무조건 메세지를 보내서 성공/실패를 보고하도록 하고 싶었다. 일단 언어는 크롤링 모듈이 python으로 만들어 졌기에 python으로 고정하고, 이제 이 메세지를 보낼 매체를 선정하는데, 선정 결과는 다음과 같았다.
- 이메일 : 프로토콜도 있고 다 있는데, 이미 내 네이버 메일함은 999+라 메일 공해를 더 일으키고 싶진 않다. 그렇다고 업무용 메일을 또 만들기엔 솔직히 내가 확인을 자주 안 할거같다.
- 카카오톡 : 접근성도 최고고, 다 좋은데 사적 메신저를 업무용으로 쓰고 싶진 않았다. 결정적으로, 현재 메세지 보내는 API 서비스가 종료하고 새로운 챗봇 서비스를 준비하는 모양인데, 이게 2020년 2월 기준으로 베타 테스트 중이라 사유를 적어서 신청해야 쓸 수 있다.
- 텔레그램 : telegram이라는 모듈이 있어서 코딩하기엔 편리한데, 이것도 사실 업무용보다는 사적인 메신저라 업무용으로 쓰고 싶진 않았다.
- Slack : slacker라는 쓰기 쉬운 모듈도 있고, 원래 업무용으로 나온 메신저다.
그래서 Slack bot을 만들기로 했다.
2. Slack bot 추가하기
일단 당연히 Slack 워크스페이스를 만들어야 한다. 다행히 만드는 건 무료다. 무료 기능은 검색 기능이 15000 메세지로 제한되는 점을 빼면 개인적으로 사용하는 수준에선 딱히 지장이 없을 것이다. 사실 실무에서 slack을 사용하고 있었다면 이미 유료 에디션일 것이다.
일단, 봇은 여기서 app이라고 부르는 데 이 app을 만들어야 한다.
https://api.slack.com/ 여기로 본인 아이디로 로그인하고, 화면 중앙의 Start building -> Create New App을 클릭한다.
본인이 쓸 앱 이름과 이 앱이 적용될 워크스페이스를 지정한다. 아까 만든 워크스페이스를 지정하면 될 것이다.
이제 앱은 생성되었고, 설정을 할 시간이다. 보면 bots와 같이 사용자와 인터렉션을 할 수 있도록 다양한 옵션이 준비되어 있는데, 지금 만드는 것은 양방향 소통이 아니라 일방적으로 정보를 전송해 주는 구독형 봇이라 Permissions만 손대보자.
들어가면 우선 Install App to Workspace라고 이름만 보면 반드시 눌러야 할 거 같이 생겼는데 안 눌리는 버튼이 있다. 당황하지 말고 지금은 권한이 아무것도 지정이 안 되어서 설치를 못 하는 것이다. 스크롤을 내리자.
밑에 Scopes에서, 우리는 Bot으로 쓰는 거니 Bot Token Scope에서 지정해보자. 여러 가지 권한들이 있는데, 내가 사용할 것은 chat:write 다. chat:write는 당연히 글을 써야 하니 주는 기능이다. 클릭하면 바로 적용이 된다.
이외에도 사용 가능 IP 제한 설정이나 리디렉션 설정 등이 있다. 필요한 설정이 있다면 하면 되고, 이제 다시 보면...
이제야 인스톨 앱 버튼이 활성화가 됐다. 다음엔 이런 권한을 준다고 확인창이 뜨는데, 읽어보고 자기가 준 권한이 맞다면 승인을 누르자.
Bot 토큰을 받는데, 이 토큰으로 봇을 조종하니 유출되지 않게 하자.
그리고 이 봇이 들어갈 채널을 만들자. 테스트인데 그냥 #test면 심심하니까 #testtest로 했다.
눌러야 하는 버튼이 보인다. Add an app을 누르자.
아까 만든 TestService가 보인다. Add를 하면 채널에 초대가 된다.
그런데, 처음 만들 땐 Add가 아니라 그냥 View로 보이면서 초대가 안 될 때가 있다. 이 경우는 그냥 기다리니까 다 해결이 됐는데, 내 추측으로는 앱이 만들어졌다는 사실 자체는 바로 전송되는데, 앱이 해당 워크스페이스에 적용되는 데는 시간이 걸리는 것 같다.
초대가 됐다. 참고로 아이콘이나 이름을 편집하고 싶다면, 앱 관리 페이지의 Basic Information에서 이름이나 아이콘 등을 바꿀 수 있다.
이걸로 모든 준비가 끝났다.
3. Python 코드 작성
pip install slacker를 통해 라이브러리를 설치한다.
사용은 놀랍도록 간단하다.
# Slackbot.pyfrom slacker import Slacker
from datetime import datetime
token = '아까 받은 토큰을 이곳에 삽입'defsend_slack_message(msg, error=''):
full_msg = msg
if error:
full_msg = msg + '\n에러 내용:\n' + str(error)
today = datetime.now().strftime('%Y-%m-%d %H:%M:%S ')
slack = Slacker(token)
slack.chat.post_message('#testtest', today + full_msg, as_user=True)
if __name__ == "__main__":
# 일부러 에러가 발생하도록 함.try:
a = 1/0except Exception as e:
send_slack_message('테스트', e)
사실 메세지 전송 자체는 딱 import 한 줄, token 한 줄, slacker 두 줄로 총 네 줄이면 충분한데, 나는 해당 에러 발생 시간도 알기 위해 시간 정보 역시 삽입했다. 아래의 __main__ 코드를 통해 에러를 발생시켜보자.
다음과 같이 메세지가 보내어진다. 기본적으론 이게 끝이다!
4. 프로젝트에서의 활용
이번엔 내가 사용했던 용례를 소개하고자 한다. 우선, 가장 기본적으론 이렇게 사용할 수 있겠다.
defrun():# 여기에서 크롤링 진행if __name__ == "__main__":
try:
run()
send_slack_message('크롤링이 완료되었습니다.')
except Exception as e:
send_slack_message('크롤링 작업 중 오류가 발생했습니다!', e)
이러면 run()이 오류 없이 진행되었을 시, 크롤링이 완료되었습니다 라는 메세지가 보내지고, 에러가 나면 에러내용과 함께 에러가 발생했다는 메세지가 전송된다. 성공을 하든 실패를 하든 일단 메세지를 보내게 된다. 만약 정해진 시간에 아무런 메세지도 안 온다? 이러면 아예 실행 자체가 안 된 것이니 역시 서버를 체크하면 된다. 성공의 경우도 메세지를 보내야 하는 이유가, 서버가 OS 다운 등의 모종의 사유로 아예 스크립트를 못 돌리는 상황도 체크하기 위함이다.
하지만 이 경우는 단순히 크롤러의 성공/실패 여부만 따질 뿐, 문제가 있다. 말 그대로 프로그램을 돌릴 수 없는 '에러' 만 잡는 상황이고, 의도하지 않는 값이 나오는 경우는 잡을 수 없다. 즉 함수에 들어가야 할 값이 안 들어가고 None이 들어가서 None이 나오는 경우, 우리가 의도한 상황이 아니지만 아무튼 에러는 안 난다. 이런 경우를 위해, 사용자 정의 에러를 사용하자.
from Slackbot import send_slack_message
classLoginError(Exception):def__init__(self, target_name):
self.msg = '{target} 로그인 양식이 맞지 않습니다.'.format(target=target_name)
send_slack_message(self.msg)
def__str__(self):return self.msg
classDBError(Exception):def__init__(self, name, length, error):
self.msg = '{name} 에 대한 {len} 행의 데이터 입력 작업 중 오류가 발생했습니다.\n{error}'\
.format(name=name, error=error, len=length)
send_slack_message(self.msg)
def__str__(self):return self.msg
코드 내에서 값의 유효성을 검증하는 부분을 추가하고, 유효하지 않을 경우 위와 같은 에러들을 raise 한다. 이 에러들은 raise 됨과 동시에 __init__ 에 의해 슬랙으로 에러 내용에 대해 정해진 양식의 메세지를 보내게 된다. 나는 실제 코드에선 Slack API가 작동하지 않을 것을 대비하여, 위의 코드에선 표시하지 않았지만 logging 모듈을 이용해 로컬 파일로 에러 로그를 작성하는 부분도 매 에러마다 추가해 두었다.
이외에도, 메세지는 보내되 크롤링은 그대로 진행해야 하는 경우도 있다. 1000개 중 하나만 오류가 난다면, 그 하나 때문에 크롤링이 완전히 중단되는 것보단 999개는 진행하고 1개는 나중에 처리하는 것이 합리적일 것이다. 그런 경우에도 간단히 이런 식으로 작성하면 될 것이다.
for name in name_list:
try:
crawl_something(name)
except:
send_slack_message('크롤링 중 {name} 를 처리하는 데 오류가 발생했습니다.'.format(name=name))
개인적으론 이 방식은 항목 하나라도 크롤링이 안되면 치명적이거나, 데이터 수가 매우 적은 경우를 제외하곤 추천하지 않는다. 천개짜리 name_list를 크롤링을 진행하는데 모두 오류가 나서 천 개의 메세지가 왔다고 가정하면... 끔찍하다. 차라리 실패한 갯수(except 블록이 실행된 횟수)를 카운팅해서 그 갯수를 보내주는 게 합리적일 것이다. 내 경우엔 일단 몇 개 정도는 당장 크롤링에 실패해도 서비스에 큰 지장은 없어서, except 블록에서 해당 에러를 로그 파일에 기록만 하도록 작성해 두었다.
5. 주의사항
위의 방법은 오류가 났다는 걸 능동적으로 알려주기만 할 뿐, 절대 오류를 해결해 주는 과정이 아님을 명심하자. 따라서 위에서는 가독성을 위해 생략했지만 문제 원인 파악과 해결을 위해선 logging 등의 기본적인 로그 작성은 필수다. 그리고, 유효성 검증을 적절하게 할 수 있도록 코드를 짜는 것 역시 중요하다.