운영 셸에서 친 오타가 오류 추적기에 올라오지 않게 before_send에서 걸러 낸다
Django 관리 명령도 앱과 같은 Sentry 초기화를 거쳐서, 운영 컨테이너에서 사람이 shell로 잘못 친 import가 앱 오류로 집계됩니다. 스택에 shell 명령 프레임이 있으면 버립니다.
운영 컨테이너에 들어가 python manage.py shell -c "..."로 데이터를 확인하다가 모듈 경로를 잘못 쳤습니다. 셸에서는 ImportError 한 줄을 보고 고쳐 치면 끝나는 일인데, 오류 추적기에는 새 앱 오류로 올라왔습니다. 알림이 가고, 오류를 보고 자동으로 진단을 돌리는 장치까지 움직였습니다. 실제 사용자는 아무 오류도 겪지 않았습니다.
원인은 Django 관리 명령도 웹 서버·워커와 같은 설정 모듈을 읽는다는 데 있습니다. 설정에서 Sentry SDK를 초기화하면 manage.py shell도 같은 DSN으로 연결되고, 처리되지 않은 예외는 똑같이 전송됩니다. SDK 입장에서는 사람이 친 오타와 요청 처리 중 난 오류를 구분할 단서가 없습니다.
해법은 before_send에서 예외 스택을 보고, Django shell 명령 파일에서 실행된 것이면 None을 돌려 버리는 것입니다. 관리 명령 전체를 끄지 않는 것이 중요합니다. 마이그레이션이나 정기 작업용 관리 명령에서 난 오류는 계속 받아야 합니다. 프레임의 module, abs_path, filename 중 무엇이 채워지는지는 환경마다 달라서 셋 다 봅니다.
SHELL_MODULE = "django.core.management.commands.shell"
SHELL_PATH = "django/core/management/commands/shell.py"
def _from_manage_shell(event):
for exc in (event.get("exception") or {}).get("values", []):
for frame in (exc.get("stacktrace") or {}).get("frames", []):
if frame.get("module") == SHELL_MODULE:
return True
for key in ("abs_path", "filename"):
if SHELL_PATH in (frame.get(key) or "").replace("\\", "/"):
return True
return False
def before_send(event, hint):
return None if _from_manage_shell(event) else event
sentry_sdk.init(dsn=SENTRY_DSN, before_send=before_send)
이 함수는 순수 함수라 시험하기 쉽습니다. 「shell 프레임이 있는 이벤트는 버린다」 「다른 관리 명령·웹 요청·워커 프레임만 있는 이벤트는 그대로 둔다」를 가짜 이벤트 몇 개로 고정해 두면, 나중에 거르는 범위가 넓어져 진짜 오류까지 삼키는 일을 막을 수 있습니다.