Kord/Docs/Plans/Audit_Channel_Plan.md

8.8 KiB

감사 채널 기획서 (Audit Log Channel Plan)

체인지로그 (Changelog)

  • 2026-03-27: 최초 작성

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)

문서 링크
기능 로드맵 Feature_Roadmap.md
권한 감사 기획서 Permission_Audit_Plan.md
에러 안내 기획서 Docs/Plans/Error_Guidance_Plan.md