감사 채널 기획서 (Audit Log Channel Plan)
체인지로그 (Changelog)
1. 개요 (Overview)
| 항목 |
내용 |
| 목표 |
관리자가 지정한 텍스트 채널에 봇의 주요 이벤트·문제 상황·서비스 상태 변동을 자동으로 기록 |
| 등록 방식 |
슬래시 명령어 (/audit-channel set, /audit-channel clear) |
| 대상 |
서버(Guild) 관리자 전용 |
| 응답 형태 |
지정 채널로 Embed 전송 (로그 메시지) |
설계 원칙
- 단일 채널 원칙: 서버당 최대 1개의 감사 채널 지정 (명확한 운영 동선)
- Severity 분류:
INFO / WARN / ERROR 3단계 심각도로 로그를 구분하여 필요 시 채널을 분리 운영 가능한 여지 확보
- Category 필터링: 기능(예: VOICE, PERMISSION 등)별로 카테고리를 나누어, 관리자가 특정 주제의 알림을 선택적으로 비활성화(Mute)할 수 있는 플래그 제공
- 비동기 큐 처리: 대량 이벤트 발생 시 Rate Limit 초과를 방지하기 위해 Promise 큐 또는 쓰로틀링 적용
- 조용한 실패 (Silent Fail): 감사 채널 전송 실패가 메인 기능을 중단시키지 않도록 격리
2. 로그 기록 대상 (Log Event Catalog)
아래는 초기 구현 대상 이벤트이며, 향후 기능 추가 시 이 테이블에 행을 추가하여 확장합니다.
| 카테고리 |
Severity |
이벤트 트리거 |
설명 |
| SYSTEM |
INFO |
봇 온라인 (ready) |
봇이 시작·재시작된 시점 |
| VOICE |
INFO |
임시 채널 생성 |
생성기 채널 입장으로 임시 음성 채널 생성 |
| VOICE |
INFO |
임시 채널 삭제 |
조건 충족으로 임시 음성 채널 삭제 |
| PERMISSION |
WARN |
권한 오버라이드 감지 |
/audit-permissions 실행 시 ⚠️ 항목 발생 |
| INVITE |
WARN |
초대 추적 실패 |
초대 정보를 불러올 수 없어 추적 중단 |
| PERMISSION |
ERROR |
권한 부족으로 기능 실패 |
PermissionDenied 에러 발생 시 |
| MIMIC |
ERROR |
웹훅 생성/전송 실패 |
Mimic 기능 수행 불가 |
| SYSTEM |
ERROR |
DB 연결 오류 |
Prisma 쿼리 실패 |
3. 등록 흐름 (Registration Flow)
3.1. 채널 설정 (/audit-channel set)
1. 명령어 실행: /audit-channel set channel:#로그채널
2. 권한 검증: 봇이 대상 채널에 Send Messages + Embed Links 보유 여부 확인
├─ 권한 없음 → ❌ "봇에게 해당 채널의 Send Messages 권한을 부여해주세요." (Ephemeral)
└─ 권한 있음 → DB UpsertC (AuditChannel 생성/갱신) → ✅ 확인 Embed 전송
3. 확인 메시지: 설정된 채널에 INFO 레벨 테스트 로그 1건 발송
3.2. 채널 해제 (/audit-channel clear)
1. 명령어 실행: /audit-channel clear
2. DB에서 guildId 기준 AuditChannel 레코드 삭제
3. Ephemeral: "감사 채널 설정이 해제되었습니다."
3.3. 현재 설정 확인 (/audit-channel status)
1. DB에서 현재 guildId의 AuditChannel 조회
2. 설정된 경우: "현재 감사 채널: #채널명" (채널 멘션 포함)
3. 미설정 경우: "설정된 감사 채널이 없습니다."
3.4. 카테고리 필터 설정 (/audit-channel filter)
1. 명령어 실행: /audit-channel filter category:VOICE state:Disable
2. DB의 AuditChannel에서 disabledCategories 배열에 'VOICE' 추가 또는 제거
3. Ephemeral: "VOICE 카테고리의 감사 로그 수신이 비활성화되었습니다."
4. UI 설계 (Embed 구성)
로그 Embed 형식
┌─────────────────────────────────────────────────────────┐
│ [Kord Audit Log] │
│ 🔴 ERROR · 2026-03-27 15:30:22 KST │
├─────────────────────────────────────────────────────────┤
│ 권한 부족으로 임시 채널 삭제 실패 │
│ │
│ 채널: #temp-gaming-🎮 │
│ 사유: Manage Channels 권한 없음 │
│ 코드: ERR_PERM_001 │
└─────────────────────────────────────────────────────────┘
| Severity |
색상 (Embed Color) |
아이콘 |
| INFO |
#5865F2 (Discord Blurple) |
🔵 |
| WARN |
#FEE75C (Discord Yellow) |
🟡 |
| ERROR |
#ED4245 (Discord Red) |
🔴 |
Embed 공통 필드
| 필드 |
내용 |
title |
[Kord Audit Log - 카테고리명] |
description |
이벤트 요약 메시지 |
color |
Severity에 따른 색상 |
timestamp |
이벤트 발생 시각 |
footer |
Severity 아이콘 + 레벨 텍스트 (예: 🔴 ERROR) |
fields |
이벤트별 상황 정보 (채널명, 에러 코드 등 선택적 추가) |
5. DB 스키마 설계 (Database Schema)
AuditChannel 모델
model AuditChannel {
guildId String @id // 서버당 1개 보장
channelId String // 전송 대상 채널 ID
disabledCategories String[] @default([]) // 전송을 무시할 로그 카테고리 목록 (예: ["VOICE", "INVITE"])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
[!NOTE]
guildId를 @id로 설정하여 서버당 1개의 감사 채널만 유지합니다. 채널 변경 시 Upsert로 처리합니다.
6. 기술 설계 (Technical Design)
서비스 구조
// src/services/AuditLogService.ts
export type AuditSeverity = 'INFO' | 'WARN' | 'ERROR';
export type AuditCategory = 'SYSTEM' | 'VOICE' | 'PERMISSION' | 'INVITE' | 'MIMIC';
export interface AuditLogPayload {
category: AuditCategory;
severity: AuditSeverity;
title: string;
description: string;
fields?: { name: string; value: string; inline?: boolean }[];
errorCode?: string; // 에러 추적용 코드 (옵션)
}
class AuditLogService {
// 로그 Embed 전송 (payload.category가 disabledCategories에 포함 시 무시)
async log(guild: Guild, payload: AuditLogPayload): Promise<void>;
// 채널 설정 (Upsert)
async setChannel(guildId: string, channelId: string): Promise<void>;
// 채널 설정 해제
async clearChannel(guildId: string): Promise<void>;
// 현재 채널 조회 및 필터(disabledCategories) 업데이트 제공
async getChannel(guildId: string): Promise<AuditChannel | null>;
}
Rate Limit 대응 전략
대량 이벤트 발생 (예: 서버 재시작, 권한 일괄 감지)으로 Discord Rate Limit(429 Too Many Requests)에 노출될 수 있습니다.
- Phase 1 (기본): 각 로그를 독립된
try/catch로 격리 → 전송 실패 시 서버 콘솔 로그만 기록
- Phase 2 (옵션): 제한 시간 내 동일 Severity 로그를 묶어 1건의 Embed로 병합하는 배치 큐 도입
기존 에러 핸들러와의 연동
src/errors/ 의 에러 핸들링 유틸리티에서 AuditLogService.log()를 callsite에서 호출하는 방식으로 연동합니다.
// 예시: 에러 발생 지점에서 감사 로그 전송
await auditLogService.log(guild, {
category: 'PERMISSION',
severity: 'ERROR',
title: '권한 부족으로 기능 실패',
description: '임시 음성 채널 삭제 불가',
fields: [{ name: '사유', value: 'Missing Permissions: MANAGE_CHANNELS' }],
errorCode: 'ERR_PERM_001',
});
7. 구현 단계 (Phased Implementation)
| 단계 |
내용 |
세부 작업 |
| Phase 1 |
인프라 구축 |
AuditChannel Prisma 모델 추가 + 마이그레이션 |
| Phase 1 |
서비스 구현 |
AuditLogService 구현 (log / setChannel / clearChannel / getChannel) |
| Phase 1 |
명령어 구현 |
/audit-channel set | clear | status | filter 슬래시 커맨드 |
| Phase 2 |
이벤트 연동 |
각 서비스(VoiceService 등)의 주요 이벤트 발생 시점에 auditLogService.log() 호출 추가 |
| Phase 2 |
Rate Limit 대응 |
배치 큐 또는 쓰로틀링 로직 도입 (필요 시) |
| Phase 3 |
i18n 연동 |
로그 메시지 텍스트를 i18n 키로 전환 |
8. 관련 문서 (References)