본문으로 건너뛰기
JSL107's Tech Note
돌아가기

크롤러의 최초 실패 원인을 운영 로그까지 전달하기

7분 분량이 글 고치기
목차 · 5

Puppeteer 크롤러의 최종 작업 로그에 같은 오류 문구만 반복돼 어디를 고쳐야 할지 알 수 없는 때가 있었어요. 목록 행을 수집하다 발생한 원본 예외와 상세 페이지에 들어갈 때 포착한 접근 거부 신호가 운영 로그까지 전달되지 않은 두 사례를 살펴봐요. 배포 후 장애 복구 시간이나 재발률은 따로 측정하지 않았으므로, 결과는 실패 원인을 구분할 근거를 로그에 남긴 범위로 한정해요.

고정 오류 문구가 목록 수집 실패를 가렸다

목록 페이지에서 게시물별 행 핸들을 얻다가 예외가 나면 크롤러는 더 진행하지 못해요. 변경 전에는 원본 예외를 콘솔에만 출력하고 상위 호출자에게는 고정된 오류를 던졌어요.

} catch (e) {
console.error(this.formatLogMessage((e as any)?.message || String(e)));
throw new Error('Failed to collect row handles');
}

운영자는 프로세스의 일시적인 콘솔이 아니라 큐 작업 상태와 실패 작업 기록에서 장애를 확인했어요. 그곳까지 전달된 것은 다시 던진 Error뿐이라 서로 다른 예외도 모두 다음 한 문장으로 기록됐어요.

Failed to collect row handles

이 문장만 봐서는 접속 거부, 페이지 종료, 차단 화면, 브라우저 탐색 실패 중 무엇이 원인인지 구분할 수 없어요. 2026년 9월 8일 기준 한 지역의 학교 홈페이지 CMS를 사용하는 게시판 십여 개가 모두 이 코드 경로에서 실패했고, 실패 작업 기록에 쌓인 수십 건의 메시지도 같았어요.

09-04~09-06   학교당 하루 소수 건
09-07         학교당 수십 건
09-08         실패 지속

특히 9월 7일부터 실패량이 학교당 하루 수십 건으로 늘어나 공통 원인이 생겼을 가능성이 있었지만, 기록만으로는 그 공통점이 무엇인지 확인할 수 없었어요.

로컬 성공으로는 운영 실패를 설명할 수 없었다

게시판 규칙이나 대상 사이트 자체에 문제가 생겼는지 확인하려고 로컬에서 실제 크롤링 작업을 slim 모드로 실행했어요. 조사한 두 게시판 모두 목록 10행을 정상적으로 수집했고, 9개 호스트에 연속으로 요청한 검사에서도 모두 HTTP 200으로 응답했으며 목록 테이블도 있었어요.

조사 당시 로컬 환경에서는 같은 실패가 재현되지 않았다는 뜻이에요. 운영 환경의 세션, 네트워크 경로, 실행 시점, 브라우저 상태까지 같았다는 증거는 아니에요. 운영 환경에는 고정 오류만 남아 있어 실패 순간의 원본 예외를 확인할 수 없었어요.

행 핸들을 수집하다 잡은 원본 예외를 최종 실패 원인에 포함해 큐와 실패 작업 기록까지 전달하도록 바꿨어요. 아래 코드는 데이터 흐름을 설명하려고 축약한 예시로, 실제 변경의 변수명이나 문자열 형식을 그대로 옮긴 것은 아니에요.

try {
// 목록 행 핸들 수집
} catch (error) {
const cause =
error instanceof Error ? error.message : String(error);
throw new Error(`Failed to collect row handles: ${cause}`);
}

Missing identifiers보다 먼저 발생한 접근 거부

두 번째 문제는 상세 페이지에 들어갈 때 드러났어요. 목록에 게시물이 있어도 상세 내용을 읽지 못하면 크롤러는 판독 불가 행 판정 로직으로 행 상태를 판단했어요. 이때 뜬 브라우저 경고창도 진단 신호로 수집하고 있었어요.

하지만 판정 결과는 행을 비공개 또는 삭제 게시물로 확정해 제외할 때만 썼어요. 제외 조건에 맞지 않으면 이미 수집한 진단 정보를 버렸고, 이후 식별자 검증에서 발생한 Missing identifiers가 최종 원인처럼 기록됐어요.

한 학교의 포토뉴스 게시판에 있던 특정 게시물에서 이 흐름을 볼 수 있었어요. 세션이 만들어지지 않은 채 상세 페이지에 들어가면 서버는 오류 상태코드 대신 HTTP 200을 반환했어요. 본문은 없고 다음 동작만 수행하는 스크립트가 들어 있었어요.

alert('게시물 열람권한이 없습니다.');
history.back();

HTTP 상태만 보면 요청은 성공한 셈이지만, 브라우저는 경고창을 띄운 뒤 목록으로 돌아가요. 데이터 흐름으로 정리하면 다음과 같아요.

상세 페이지 요청
↓
HTTP 200 + 열람권한 경고창
↓
진단 신호에 경고 문구 수집
↓
history.back()으로 목록 복귀
↓
URL에서 게시물 식별자 매개변수 제거
↓
URL 기반 게시물 식별자 추출 실패
↓
식별자 누락 오류 기록

이 게시판 규칙은 URL의 게시물 식별자 매개변수에서 게시물 식별자를 읽었어요. history.back()으로 목록에 돌아오자 URL에서 글번호가 사라졌고, 이어 식별자 추출도 실패했어요. 식별자 누락은 실제로 일어났지만 접근 거부 뒤에 나타난 2차 증상이었어요. 대시보드 힌트는 이를 “식별자 누락 — 룰 셀렉터/리다이렉트 확인”으로 안내해 운영자가 세션이나 접근 권한보다 크롤링 규칙을 먼저 의심하게 했어요.

2026년 9월 7일 04시 32분부터 9월 8일 03시 32분까지 대시보드의 실패 작업 기록에는 식별자 누락 오류가 십여 건 남았어요. 같은 기간 9월 7일자 새 글도 수집되지 않았지만, 브라우저가 확보한 “게시물 열람권한이 없습니다”라는 문구는 최종 로그 어디에도 남지 않았어요.

이미 수집한 신호를 실패 기록에 연결했다

판독 불가 행 판정 결과와 이미 수집한 경고창 문구가 상세 진입 실패 원인까지 전달되도록 경로를 이었어요. 변경 전후의 차이는 다음과 같아요.

실패 단계변경 전 최종 기록변경 후 남길 수 있는 근거
목록 행 핸들 수집Failed to collect row handles고정 문구와 원본 예외
상세 접근 거부Missing identifiers식별자 누락과 상세 진입 판정·경고창
비공개·삭제 확정제외 판정에 진단 정보 사용기존 동작 유지
판정했지만 제외하지 않은 행판정 정보 폐기후속 실패 원인에 판정 정보 포함

경고창 문구만으로 최종 오류를 바꾸지는 않았어요. 게시물 식별자가 누락됐다는 결과와 상세 진입이 거부됐다는 선행 원인을 함께 남겨야 식별자가 왜 없어졌는지 추적할 수 있으니까요.

개발 브랜치에 반영한 상세 진입 진단 변경은 대시보드 브랜치에도 적용했어요. 변경 설명에는 두 변경의 목적이 같다는 사실까지만 기록돼 있어 cherry-pick이나 merge 같은 특정 동기화 방식을 썼다고 단정할 수는 없어요. 대시보드는 장애를 읽고 분류하므로 실행 브랜치에 따라 기록되는 실패 정보가 달라지지 않도록 같은 진단 경로를 유지해야 했어요.

남은 한계와 검증

원본 예외와 경고창을 기존 Error 메시지에 포함하면 큐와 실패 작업 기록으로 전달되는 구조를 그대로 쓸 수 있어 변경 범위가 작아요. 다만 문자열이 길어지고, 대시보드가 문구를 직접 분류한다면 표현이 바뀔 때 영향을 받을 수 있어요. 장기적으로는 실패 정보를 구조화하는 방안도 고려할 수 있어요.

type CrawlerFailure = {
stage: 'ROW_COLLECTION' | 'DETAIL_ENTRY' | 'IDENTIFIER_CHECK';
code: string;
message: string;
cause?: string;
dialogs?: string[];
judgment?: string;
};

이 구조를 쓰면 대시보드는 message를 해석하지 않고 stage와 code로 실패를 분류할 수 있어요. 이번에는 오류 스키마를 개편한 것이 아니라, 기존 실패 원인에 이미 수집한 정보를 포함했어요.

경고창과 원본 예외에는 사용자 정보, URL 매개변수, 내부 경로 또는 인증 관련 값이 들어갈 수 있어요. 운영 로그에 싣기 전에 마스킹과 길이 제한을 검토해야 해요. 재시도로 같은 예외가 반복될 때 저장량과 알림량이 얼마나 늘어나는지도 별도 운영 정책으로 다뤄야 해요.

이번 변경으로 한 지역의 CMS 게시판 십여 개에서 발생한 실패 원인까지 해결한 것은 아니에요. 기존 실패 작업 기록 수십 건에서 사라진 원본 예외도 복원할 수 없어요. 학교 게시판 사례에서는 접근 거부가 식별자 누락보다 먼저 발생했고 같은 기간 새 글이 수집되지 않았다는 점을 확인했지만, 변경 후 해당 게시물이 정상적으로 수집됐는지, 식별자 누락 오류 십여 건이 모두 해소됐는지는 알 수 없어요.

최소 회귀 검증에서는 행 핸들 수집 예외가 실패 작업 기록까지 전달되는지 확인해야 해요. HTTP 200 응답이 경고창과 history.back()을 실행할 때도 접근 거부 원인이 식별자 누락과 함께 남는지 검증해야 해요. 재시도·중복 실행·작업 재시작 후에도 같은 진단 정보가 보존되는지는 별도 검증 대상으로 남아 있어요.


이 글 고치기
이 글 공유하기:

이전 글
A2A로 구분하는 내부 워커와 독립 에이전트
다음 글
OpenTelemetry GenAI 규약으로 에이전트 장애 경계 추적하기