fix: add comprehensive error handling for P1 critical issues
P1-1: Callback query error handling - Add try-catch around domain registration and server order - Send user-friendly error messages on failure - Use answerCallbackQuery to acknowledge button clicks - Add structured logging with createLogger P1-2: Queue DLQ monitoring - Add admin notification when server provisioning fails - Update order status to 'failed' in database - Include detailed context in notifications - Apply rate limiting (1 notification per hour) P1-3: Email handler error recovery - Add admin notification when SMS parsing fails - Include email preview in notifications - Mask email addresses for privacy - Add structured logging with emailLogger Also add 'failed' status to ServerOrder type. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
48
src/index.ts
48
src/index.ts
@@ -9,6 +9,7 @@ import { reconcileDeposits, formatReconciliationReport } from './utils/reconcili
|
||||
import { handleProvisionQueue, handleProvisionDLQ } from './server-provision';
|
||||
import { timingSafeEqual } from './security';
|
||||
import { createLogger } from './utils/logger';
|
||||
import { notifyAdmin } from './services/notification';
|
||||
|
||||
const logger = createLogger('worker');
|
||||
|
||||
@@ -101,23 +102,50 @@ Documentation: https://github.com/your-repo
|
||||
|
||||
// Email 핸들러 (SMS → 메일 → 파싱)
|
||||
async email(message: EmailMessage, env: Env): Promise<void> {
|
||||
const emailLogger = createLogger('email-handler');
|
||||
|
||||
try {
|
||||
// 이메일 본문 읽기
|
||||
const rawEmail = await new Response(message.raw).text();
|
||||
|
||||
// 이메일 주소 마스킹
|
||||
const maskedFrom = message.from.replace(/@.+/, '@****');
|
||||
logger.info('이메일 수신', { from: maskedFrom, size: message.rawSize });
|
||||
emailLogger.info('이메일 수신', { from: maskedFrom, size: message.rawSize });
|
||||
|
||||
// SMS 내용 파싱
|
||||
const notification = await parseBankSMS(rawEmail, env);
|
||||
if (!notification) {
|
||||
logger.info('은행 SMS 파싱 실패');
|
||||
return;
|
||||
// Structured logging with context
|
||||
emailLogger.warn('SMS 파싱 실패', {
|
||||
from: maskedFrom,
|
||||
subject: message.headers.get('subject') || 'N/A',
|
||||
preview: rawEmail.substring(0, 200).replace(/\s+/g, ' '),
|
||||
size: message.rawSize
|
||||
});
|
||||
|
||||
// Admin notification for manual review
|
||||
await notifyAdmin(
|
||||
'api_error',
|
||||
{
|
||||
service: 'Email Handler',
|
||||
error: 'SMS parsing failed',
|
||||
context: `From: ${maskedFrom}\nSubject: ${message.headers.get('subject') || 'N/A'}\nPreview: ${rawEmail.substring(0, 150).replace(/\s+/g, ' ')}...`
|
||||
},
|
||||
{
|
||||
telegram: {
|
||||
sendMessage: (chatId: number, text: string) =>
|
||||
sendMessage(env.BOT_TOKEN, chatId, text)
|
||||
},
|
||||
adminId: env.DEPOSIT_ADMIN_ID || '',
|
||||
env
|
||||
}
|
||||
);
|
||||
|
||||
return; // Don't throw - email routing expects success
|
||||
}
|
||||
|
||||
// 파싱 결과 마스킹 로깅
|
||||
logger.info('SMS 파싱 결과', {
|
||||
emailLogger.info('SMS 파싱 결과', {
|
||||
bankName: notification.bankName,
|
||||
depositorName: notification.depositorName
|
||||
? notification.depositorName.slice(0, 2) + '***'
|
||||
@@ -144,16 +172,16 @@ Documentation: https://github.com/your-repo
|
||||
).run();
|
||||
|
||||
const notificationId = insertResult.meta.last_row_id;
|
||||
logger.info('알림 저장 완료', { notificationId });
|
||||
emailLogger.info('알림 저장 완료', { notificationId });
|
||||
|
||||
// 자동 매칭 시도
|
||||
const matched = await matchPendingDeposit(env.DB, notificationId, notification);
|
||||
|
||||
// 매칭 결과 로깅 (민감 정보 마스킹)
|
||||
if (matched) {
|
||||
logger.info('자동 매칭 성공', { transactionId: matched.transactionId });
|
||||
emailLogger.info('자동 매칭 성공', { transactionId: matched.transactionId });
|
||||
} else {
|
||||
logger.info('매칭되는 거래 없음');
|
||||
emailLogger.info('매칭되는 거래 없음');
|
||||
}
|
||||
|
||||
// 매칭 성공 시 사용자에게 알림
|
||||
@@ -196,7 +224,11 @@ Documentation: https://github.com/your-repo
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('이메일 처리 오류', error as Error);
|
||||
emailLogger.error('이메일 처리 오류', error as Error, {
|
||||
from: message.from.replace(/@.+/, '@****'),
|
||||
size: message.rawSize
|
||||
});
|
||||
// Don't rethrow - email routing expects success response
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user