feat(backend): 봇 연속 오류 시 자동 정지 로직 추가
- consecutiveErrors 카운터로 실패 횟수 추적 (성공 시 0으로 리셋) - 동일 에러 루프에서 sync/error 로그는 첫 1회만 기록하여 스팸 방지 - 10회 연속 실패 시 stopBot 호출 및 bot/stop 로그 1건 남김 - docs/logs.md, docs/development.md 관련 설명 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f73314d5dc
commit
8ece4b1850
3 changed files with 58 additions and 8 deletions
|
|
@ -7,6 +7,7 @@ import { logActivity } from '../utils/log.js';
|
|||
|
||||
const REDIS_PREFIX = 'bot:status:';
|
||||
const TIMEZONE = 'Asia/Seoul';
|
||||
const MAX_CONSECUTIVE_ERRORS = 10;
|
||||
|
||||
async function schedulerPlugin(fastify, opts) {
|
||||
const tasks = new Map();
|
||||
|
|
@ -121,6 +122,7 @@ async function schedulerPlugin(fastify, opts) {
|
|||
totalAdded: 0,
|
||||
lastSyncDuration: null,
|
||||
errorMessage: null,
|
||||
consecutiveErrors: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -150,6 +152,7 @@ async function schedulerPlugin(fastify, opts) {
|
|||
const updateData = {
|
||||
lastCheckAt: nowKST(),
|
||||
totalAdded: (status.totalAdded || 0) + result.addedCount,
|
||||
consecutiveErrors: 0,
|
||||
};
|
||||
if (setRunningStatus) {
|
||||
updateData.status = 'running';
|
||||
|
|
@ -214,19 +217,41 @@ async function schedulerPlugin(fastify, opts) {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const prev = await getStatus(botId);
|
||||
const consecutiveErrors = (prev.consecutiveErrors || 0) + 1;
|
||||
await updateStatus(botId, {
|
||||
status: 'error',
|
||||
lastCheckAt: nowKST(),
|
||||
errorMessage: err.message,
|
||||
consecutiveErrors,
|
||||
});
|
||||
fastify.log.error(`[${botId}] 동기화 오류: ${err.message}`);
|
||||
logActivity(fastify.db, {
|
||||
actor: botId,
|
||||
action: 'error',
|
||||
category: 'sync',
|
||||
summary: `${botId} 동기화 오류: ${err.message}`,
|
||||
details: { error: err.message },
|
||||
});
|
||||
fastify.log.error(`[${botId}] 동기화 오류 (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS}): ${err.message}`);
|
||||
// 첫 오류만 activity log에 기록 (중복 스팸 방지)
|
||||
if (consecutiveErrors === 1) {
|
||||
logActivity(fastify.db, {
|
||||
actor: botId,
|
||||
action: 'error',
|
||||
category: 'sync',
|
||||
summary: `${botId} 동기화 오류: ${err.message}`,
|
||||
details: { error: err.message },
|
||||
});
|
||||
}
|
||||
// 임계값 도달 시 봇 자동 정지
|
||||
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
||||
fastify.log.warn(`[${botId}] 연속 ${MAX_CONSECUTIVE_ERRORS}회 실패 - 자동 정지`);
|
||||
logActivity(fastify.db, {
|
||||
actor: botId,
|
||||
action: 'stop',
|
||||
category: 'bot',
|
||||
summary: `${botId} 연속 ${MAX_CONSECUTIVE_ERRORS}회 실패로 자동 정지`,
|
||||
details: { error: err.message, consecutiveErrors },
|
||||
});
|
||||
try {
|
||||
await stopBot(botId);
|
||||
} catch (stopErr) {
|
||||
fastify.log.error(`[${botId}] 자동 정지 실패: ${stopErr.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { timezone: TIMEZONE });
|
||||
|
||||
|
|
|
|||
|
|
@ -283,6 +283,22 @@ queryClient.invalidateQueries();
|
|||
|
||||
---
|
||||
|
||||
## X 봇 / Nitter
|
||||
|
||||
X 봇은 `/docker/nitter/`의 Nitter 인스턴스(zedeus/nitter)를 스크래핑하여 트윗을 수집합니다. 백엔드는 `NITTER_URL`(기본값 `http://nitter:8080`)로 접속합니다.
|
||||
|
||||
### 세션 관리 (`sessions.jsonl`)
|
||||
X는 비로그인 API 접근을 막고 있어, Nitter는 `/docker/nitter/sessions.jsonl`에 저장된 실제 X 계정 쿠키(`auth_token`, `ct0`)로 요청을 보냅니다.
|
||||
|
||||
- 세션이 만료/차단되면 Nitter 측에서 `no sessions available for API` 로그가 찍히고 SIGSEGV로 크래시 → 백엔드에서 `[x-N] 동기화 오류: 요청 타임아웃` 반복 (단, 연속 10회 실패 시 자동 정지 — `logs.md` 참조)
|
||||
- `renew_sessions.py`가 매시 세션을 점검하지만, 판별 기준(`check_nitter()`)이 약하면 만료 상태에서도 "정상"으로 오판할 수 있음 → 기준은 트윗 본문(`tweet-content` 블록) 렌더 여부로 유지할 것
|
||||
- 수동 갱신: `python3 /docker/nitter/create_session_curl.py <username> <password>` 로 새 쿠키 발급 후 `sessions.jsonl` 두 줄을 덮어쓰고 `docker compose restart nitter` 실행
|
||||
|
||||
### 포크 관련 메모
|
||||
`unixfox/nitter` 같은 구버전 기반 포크는 sessions.jsonl을 아예 인식하지 못해 트윗 수집이 불가능합니다. 교체 시에는 바이너리에 sessions 처리 심볼이 있는지 확인할 것(예: `strings nitter | grep sessions.jsonl`).
|
||||
|
||||
---
|
||||
|
||||
## 활동 로그 시스템
|
||||
|
||||
관리자/봇의 모든 활동을 `logs` 테이블에 기록하고 관리자 페이지에서 조회.
|
||||
|
|
|
|||
|
|
@ -100,6 +100,15 @@ logActivity(db, { actor, action, category, targetType, targetId, summary, detail
|
|||
|
||||
> **봇 로그 전략:** 변화 없는 동기화는 로그 안 남김. `addedCount > 0`이거나 에러인 경우만 기록.
|
||||
|
||||
### 연속 오류 시 자동 정지
|
||||
|
||||
`plugins/scheduler.js`의 `MAX_CONSECUTIVE_ERRORS`(기본 10)로 제어.
|
||||
|
||||
- Redis의 bot status에 `consecutiveErrors` 카운터를 유지. 성공 시 0으로 리셋, 실패 시 +1.
|
||||
- 동일한 에러 루프에서 `sync/error` 로그는 **첫 1회만** 기록 (로그 테이블 스팸 방지).
|
||||
- 카운터가 `MAX_CONSECUTIVE_ERRORS`에 도달하면 `stopBot()`을 호출해 cron 태스크를 내리고, `bot/stop` action으로 *"${botId} 연속 N회 실패로 자동 정지"* 로그 1건을 남김.
|
||||
- 자동 정지된 봇은 원인 조치 후 관리자 UI에서 수동으로 다시 시작해야 함.
|
||||
|
||||
---
|
||||
|
||||
## 프론트엔드 구현
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue