알림을 보내는 코드가 알림을 못 보내고 있었다
봇 여러 개를 혼자 돌리다 보면 바라는 건 결국 하나입니다. 무언가 바뀌거나 망가졌을 때 휴대폰이 울리는 것입니다. 그래서 게이트 상태가 바뀌면 텔레그램으로 메시지를 보내는 send() 함수를 만들어 두었습니다. 파이썬 표준 라이브러리 urllib으로 Bot API에 요청을 보내는 짧은 코드였습니다.
그런데 이 머신에서는 그 요청이 SSL 단계에서 깨졌습니다. 이 맥에는 사내용 자체 서명 인증서 체인이 깔려 있는데, urllib이 그 체인을 통과하지 못했습니다. 같은 머신의 시스템 curl로는 문제없이 나갔습니다.
두 번째 함정은 launchd였습니다. 터미널에서 실행하면 셸 환경변수에 토큰이 있으니 잘 돌아갑니다. 하지만 launchd 잡으로 돌 때는 그 환경변수가 주입되지 않습니다. 셸에서 테스트하면 통과하는데, 정작 스케줄로 돌 때는 자격증명이 비어 있는 구조였습니다.
알림 코드를 터미널에서 한 번 실행해 보고 "된다"고 확인한 적이 있다면, 그 확인은 실제로 스케줄러가 돌릴 때와 같은 네트워크 경로, 같은 환경에서 한 것이었습니까?
같은 함수, 다른 세계
여기서 헷갈렸던 건 코드 자체에는 버그가 없었다는 점입니다. URL도 맞고 페이로드도 맞았습니다. 달랐던 건 두 가지 실행 조건이었습니다.
- TLS 검증에 쓰는 신뢰 경로: 파이썬 런타임의 경로와 시스템
curl의 경로가 같지 않았고, 이 머신의 인증서 체인은 앞쪽에서만 막혔습니다. - 환경: 대화형 셸에는 있는 환경변수가 launchd에는 없었습니다.
그래서 개발할 때 보는 화면과 매일 실제로 돌아가는 잡이 다른 결과를 냈습니다.
당신이라면? 런타임의 인증서 설정을 파고들어 표준 라이브러리 경로를 고치겠습니까, 아니면 이 머신에서 이미 동작하는 경로를 빌리겠습니까?
고친 것: 시스템 curl, 그리고 0600 파일
저는 후자를 골랐습니다.
send()는 이제 시스템curl을 셸로 호출합니다. 이 머신에서 이미 통과하는 TLS 경로를 그대로 씁니다.- 자격증명은 환경변수를 먼저 보고, 없으면 홈 디렉터리의 설정 폴더에 있는
fleet_alert.env파일에서 읽습니다. 파일 권한은 0600입니다. launchd에 환경을 주입하지 않아도 같은 값을 읽게 하려는 것입니다.
런타임에 인증서 번들을 직접 지정하는 방법도 있었을 겁니다. 이번 커밋에서 선택한 건 curl이었고, 그만큼 이 해결은 이 머신에 묶여 있습니다.
나머지 절반: 무엇을 고장이라고 부를 것인가
발송 경로가 열리자 보낼 내용을 정해야 했습니다. 게이트 전환 알림과 별도로, 봇이 망가지는 순간도 알고 싶었습니다. 그래서 fleet_events.py를 추가했습니다.
- 쇼츠, Threads, Instagram 등 다섯 개 봇의 자동 실행 로그를 스캔합니다.
- 고장 마커가 있는 줄만 고릅니다.
Traceback, 업로드/게시 실패, 토큰 만료, 쿼터, HTTP 4xx/5xx입니다. - 의도된 무동작은 제외합니다.
신호부족,DRY,게이트 대기같은 줄입니다.
세 번째 규칙이 생각보다 중요했습니다. 제 봇들은 일부러 아무것도 안 하는 날이 많습니다. 신호가 부족하면 쉬고, 드라이런이면 게시하지 않고, 게이트가 열리지 않으면 기다립니다. 겉으로는 "아무 일도 없었다"로 보이지만 설계대로 동작한 결과입니다. 이런 줄에서 알림이 울리면 진짜 고장 알림이 그 사이에 묻힙니다.
같은 줄을 매일 다시 보내지 않도록 스캔은 바이트 오프셋 기준입니다. 파일별로 어디까지 읽었는지 상태로 저장하고, 그 뒤에 새로 생긴 줄만 봅니다. 한 줄은 한 번만 알립니다. 로그 파일이 잘려서 저장된 오프셋보다 짧아지면 처음부터 다시 읽고, 파일이 없으면 건너뜁니다.
둘 다 쇼츠 자동화 파이프라인의 일일 실행에 연결했습니다. 하루 한 번 전체 로그를 훑습니다.
테스트는 네 가지입니다. 마커를 잡는지와 무동작 줄을 건너뛰는지, 오프셋이 전진하는지, 파일이 잘렸을 때 리셋되는지, 파일이 없을 때 죽지 않는지입니다.
자가진단 체크리스트
- 알림 발송을 터미널이 아니라 실제 스케줄러(launchd, cron)와 같은 환경에서 한 번이라도 보내 봤습니까?
- 로그 감시가 '의도된 무동작' 줄과 '고장' 줄을 구분하는 명시적인 목록을 갖고 있습니까?
- 오프셋으로 로그를 읽는다면, 파일이 잘리거나 사라졌을 때의 동작을 테스트로 확인했습니까?
솔직한 부분
이 커밋 전에 실제로 몇 건의 알림이 나가지 못했는지는 기록이 없어 모릅니다. 고장 감지는 문자열 마커 매칭이라 목록에 없는 형태의 실패 메시지는 놓칩니다. 스캔도 하루 한 번이라 실시간 감시가 아닙니다. curl로 우회한 건 이 머신의 인증서 환경에 기댄 해결이라 다른 머신으로 옮기면 다시 확인해야 합니다. 0600 권한의 설정 파일도 결국 평문 파일입니다.