TIL - 20260830

juni·2026년 8월 29일

TIL

목록 보기
444/468

0830 백엔드 아키텍처 고도화 (7/N): Audit Log, Event Log와 운영 추적성


✅ 1. 운영 추적성이란 무엇인가?

  • 운영 추적성은 서비스에서 어떤 일이 언제, 누구에 의해, 어떤 이유로 발생했는지 확인할 수 있는 능력입니다.
  • 관리자 화면이 커질수록 단순히 데이터의 현재 상태만 보는 것으로는 부족합니다.
  • 상담 상태가 왜 바뀌었는지, 상품 지원금이 누가 수정했는지, 엑셀 다운로드를 누가 요청했는지, 알림톡이 왜 실패했는지 추적할 수 있어야 합니다.
현재 데이터:
상담 상태 = CANCELED

운영 추적:
누가
언제
어떤 상태에서
어떤 상태로
왜
변경했는가?

➕ 1-1. 운영 추적성이 없을 때 생기는 문제

상담 상태가 왜 바뀌었는지 모름
상품 가격/지원금 변경 원인을 모름
고객에게 알림톡이 갔는지 확인 어려움
엑셀 파일을 누가 다운로드했는지 모름
권한 변경 이력을 추적할 수 없음
장애 발생 시 원인 파악이 늦어짐
  • 운영 추적성은 “나중에 문제 생겼을 때 보는 기록”입니다.
  • 문제가 터진 뒤에는 과거 기록을 만들 수 없습니다.

✅ 2. Audit Log와 Event Log의 차이

  • Audit Log와 Event Log는 비슷해 보이지만 목적이 다릅니다.
구분목적예시
Audit Log누가 중요한 작업을 했는지 추적관리자가 상품 가격 수정
Event Log시스템에서 어떤 사건이 발생했는지 기록상담 신청 생성 이벤트 발생
Application Log개발/장애 분석용 로그API error stack trace
Access Log요청 접근 기록Nginx access log
Audit Log:
관리자/사용자 행위 중심

Event Log:
도메인 사건 중심

Application Log:
시스템 동작/에러 중심

➕ 2-1. 간단한 예시

상담 상태 변경

Audit Log:
관리자 3번이 상담 100번 상태를 CALLING → CALLED로 변경

Event Log:
ConsultStatusChanged 이벤트 발생

Application Log:
updateConsultStatusUseCase completed in 142ms
  • Audit Log는 책임과 추적성에 가깝습니다.
  • Event Log는 시스템 흐름과 후속 처리에 가깝습니다.

✅ 3. Audit Log가 필요한 작업

  • 모든 작업을 Audit Log로 남길 필요는 없습니다.
  • 운영 데이터에 영향을 주거나, 보안·개인정보·권한과 연결된 작업을 중심으로 남기는 것이 좋습니다.

➕ 3-1. 상담 관련

상담 상태 변경
상담 메모 추가/수정/삭제
상담 담당자 변경
상담 개인정보 열람
상담 엑셀 다운로드
상담 데이터 익명화

➕ 3-2. 상품/배너 관련

상품 등록
상품 수정
상품 가격/지원금 변경
상품 노출 상태 변경
상품 삭제/복구
배너 등록/수정/삭제
배너 노출 순서 변경

➕ 3-3. 관리자/권한 관련

관리자 계정 생성
관리자 계정 비활성화
관리자 권한 변경
관리자 로그인 실패 반복
관리자 강제 로그아웃
비밀번호 초기화

➕ 3-4. 외부 연동/파일 관련

엑셀 Export 요청
엑셀 파일 다운로드
알림톡 수동 재발송
Webhook 수동 재처리
외부 API Secret 변경
S3 파일 삭제
  • 특히 엑셀 다운로드, 권한 변경, 상품 가격/지원금 변경은 반드시 Audit Log 대상으로 보는 것이 좋습니다.
  • 고객 개인정보와 연결되는 작업은 더 보수적으로 기록해야 합니다.

✅ 4. Audit Log 테이블 설계

  • Audit Log는 공통 테이블로 설계하는 것이 좋습니다.
  • 여러 도메인에서 공통으로 사용할 수 있어야 합니다.
model AuditLog {
  id          Int       @id @default(autoincrement())

  actorType   String
  actorId     Int?
  actorName   String?

  action      String

  targetType  String
  targetId    String?

  beforeValue Json?
  afterValue  Json?
  metadata    Json?

  requestId   String?
  ipAddress   String?
  userAgent   String?

  createdAt   DateTime  @default(now())

  @@index([actorType, actorId, createdAt])
  @@index([targetType, targetId, createdAt])
  @@index([action, createdAt])
  @@index([requestId])
}

➕ 4-1. 핵심 컬럼 의미

컬럼의미
actorType작업 주체 종류
actorId작업 주체 ID
action수행한 작업
targetType대상 도메인
targetId대상 ID
beforeValue변경 전 값
afterValue변경 후 값
metadata추가 정보
requestId요청 추적 ID
ipAddress요청 IP
userAgent요청 환경

➕ 4-2. actorType 예시

ADMIN
CUSTOMER
SYSTEM
WORKER
WEBHOOK
CRON

➕ 4-3. targetType 예시

CONSULT
PRODUCT
PRODUCT_OPTION
BANNER
ADMIN_USER
EXPORT_JOB
NOTIFICATION_JOB
WEBHOOK_EVENT
  • Audit Log는 다양한 도메인에서 쓰이므로 너무 특정 테이블에만 맞추지 않는 것이 좋습니다.
  • targetType + targetId 구조가 있으면 어느 대상의 이력인지 쉽게 찾을 수 있습니다.

✅ 5. Audit Action 설계

  • action은 Audit Log의 핵심입니다.
  • action 이름만 봐도 어떤 작업인지 이해할 수 있어야 합니다.

➕ 5-1. 좋은 action 이름

CONSULT_STATUS_UPDATE
CONSULT_MEMO_CREATE
CONSULT_MEMO_UPDATE
CONSULT_EXPORT_REQUEST
CONSULT_ANONYMIZE

PRODUCT_CREATE
PRODUCT_UPDATE
PRODUCT_PRICE_UPDATE
PRODUCT_SOFT_DELETE
PRODUCT_RESTORE

BANNER_UPDATE
BANNER_DISPLAY_ORDER_UPDATE

NOTIFICATION_RESEND
EXPORT_FILE_DOWNLOAD

ADMIN_USER_CREATE
ADMIN_USER_DISABLE
ADMIN_ROLE_UPDATE
ADMIN_PERMISSION_GRANT
ADMIN_PERMISSION_REVOKE

➕ 5-2. 나쁜 action 이름

UPDATE
CHANGE
SAVE
DELETE
PROCESS
HANDLE
WORK

문제:

무엇을 했는지 불명확
검색/필터가 어려움
운영자가 이해하기 어려움
장애 분석에 도움 부족

➕ 5-3. 설계 기준

도메인_행위 형태
위험 작업은 action을 분리
운영자가 이해 가능한 이름
권한 코드와 어느 정도 대응 가능하게 설계
  • 권한 코드와 Audit action을 완전히 같게 만들 필요는 없습니다.
  • 하지만 CONSULT_EXPORT 권한과 CONSULT_EXPORT_REQUEST action처럼 의미가 연결되면 관리하기 좋습니다.

✅ 6. beforeValue / afterValue 설계

  • Audit Log에서 변경 전/후 값을 저장하면 나중에 무엇이 바뀌었는지 확인할 수 있습니다.
  • 하지만 전체 row를 그대로 저장하면 개인정보와 불필요한 데이터가 과하게 남을 수 있습니다.

➕ 6-1. 좋은 예시

{
  "beforeValue": {
    "status": "CALLING"
  },
  "afterValue": {
    "status": "CALLED"
  }
}
{
  "beforeValue": {
    "supportAmount": 300000
  },
  "afterValue": {
    "supportAmount": 350000
  }
}

➕ 6-2. 나쁜 예시

상담 row 전체 저장
고객 전화번호 원본 저장
상담 메모 전체 저장
관리자 passwordHash 저장
외부 API token 저장

➕ 6-3. 기준

변경된 필드 중심으로 저장
개인정보 원본 제외
Secret/Token 제외
긴 텍스트는 요약 또는 별도 정책
민감한 필드는 마스킹
  • Audit Log는 오래 보관될 가능성이 높습니다.
  • 그래서 개인정보 원본을 넣으면 나중에 보안 리스크가 커집니다.

✅ 7. AuditLogRepository

  • Audit Log 저장은 여러 Use Case에서 반복됩니다.
  • 공통 Repository로 분리해두면 저장 기준을 일관되게 만들 수 있습니다.
@Injectable()
export class AuditLogRepository {
  constructor(private readonly prisma: PrismaService) {}

  create(
    params: {
      actorType: string;
      actorId?: number;
      actorName?: string;
      action: string;
      targetType: string;
      targetId?: string;
      beforeValue?: Prisma.InputJsonValue;
      afterValue?: Prisma.InputJsonValue;
      metadata?: Prisma.InputJsonValue;
      requestId?: string;
      ipAddress?: string;
      userAgent?: string;
    },
    tx: PrismaTx = this.prisma,
  ) {
    return tx.auditLog.create({
      data: {
        actorType: params.actorType,
        actorId: params.actorId,
        actorName: params.actorName,
        action: params.action,
        targetType: params.targetType,
        targetId: params.targetId,
        beforeValue: params.beforeValue,
        afterValue: params.afterValue,
        metadata: params.metadata,
        requestId: params.requestId,
        ipAddress: params.ipAddress,
        userAgent: params.userAgent,
      },
    });
  }
}

➕ 7-1. 주의

Repository는 저장만 담당
무엇을 저장할지는 Use Case에서 결정
민감정보 필터링 책임을 명확히 해야 함
  • AuditLogRepository가 모든 마스킹을 알아서 해준다고 믿으면 위험합니다.
  • Use Case에서 저장할 값을 선별하고, 공통 sanitizer를 추가로 두는 방식이 좋습니다.

✅ 8. AuditLogFactory / Sanitizer

  • Audit Log에 민감정보가 들어가는 것을 줄이려면 Factory나 Sanitizer를 둘 수 있습니다.
  • 특히 before/after를 만들 때 공통 필터링이 필요합니다.

➕ 8-1. Sanitizer 예시

const SENSITIVE_KEYS = [
  'password',
  'passwordHash',
  'token',
  'accessToken',
  'refreshToken',
  'authorization',
  'cookie',
  'phone',
  'phoneNormalized',
  'recipientEncrypted',
  'secret',
  'apiKey',
];

export function sanitizeAuditValue(value: unknown) {
  if (!value || typeof value !== 'object') {
    return value;
  }

  return Object.fromEntries(
    Object.entries(value as Record<string, unknown>).map(([key, val]) => {
      if (SENSITIVE_KEYS.some((sensitive) =>
        key.toLowerCase().includes(sensitive.toLowerCase()),
      )) {
        return [key, '[REDACTED]'];
      }

      return [key, val];
    }),
  );
}

➕ 8-2. Factory 예시

export class AuditLogFactory {
  static productPriceUpdated(params: {
    adminId: number;
    productId: number;
    beforeSupportAmount: number;
    afterSupportAmount: number;
    requestId?: string;
  }) {
    return {
      actorType: 'ADMIN',
      actorId: params.adminId,
      action: 'PRODUCT_PRICE_UPDATE',
      targetType: 'PRODUCT',
      targetId: String(params.productId),
      beforeValue: {
        supportAmount: params.beforeSupportAmount,
      },
      afterValue: {
        supportAmount: params.afterSupportAmount,
      },
      requestId: params.requestId,
    };
  }
}

➕ 8-3. 장점

action별 저장 값 표준화
민감정보 저장 위험 감소
Use Case 코드 간결화
테스트 가능
  • 처음부터 모든 action에 Factory를 만들 필요는 없습니다.
  • 상품 가격 변경, 권한 변경, 엑셀 다운로드처럼 중요한 작업부터 적용하면 됩니다.

✅ 9. Use Case에서 Audit Log 저장하기

  • Audit Log는 중요한 DB 변경과 같은 transaction으로 묶는 것이 좋습니다.
  • 예를 들어 상품 가격 변경과 Audit Log 저장은 함께 성공하거나 함께 실패해야 합니다.
Transaction 시작
  ↓
상품 가격 변경
  ↓
Audit Log 저장
  ↓
commit

➕ 9-1. 상품 가격 변경 예시

@Injectable()
export class UpdateProductPriceUseCase {
  constructor(
    private readonly prisma: PrismaService,
    private readonly productRepository: ProductRepository,
    private readonly auditLogRepository: AuditLogRepository,
  ) {}

  async execute(command: UpdateProductPriceCommand) {
    return this.prisma.$transaction(async (tx) => {
      const product = await this.productRepository.findByIdOrThrow(
        command.productId,
        tx,
      );

      const updated = await this.productRepository.updatePrice(
        {
          productId: command.productId,
          supportAmount: command.supportAmount,
        },
        tx,
      );

      await this.auditLogRepository.create(
        {
          actorType: 'ADMIN',
          actorId: command.adminId,
          action: 'PRODUCT_PRICE_UPDATE',
          targetType: 'PRODUCT',
          targetId: String(command.productId),
          beforeValue: {
            supportAmount: product.supportAmount,
          },
          afterValue: {
            supportAmount: updated.supportAmount,
          },
          requestId: command.requestId,
          ipAddress: command.ipAddress,
          userAgent: command.userAgent,
        },
        tx,
      );

      return updated;
    });
  }
}

➕ 9-2. 기준

DB 변경과 Audit Log는 같은 transaction
외부 API 호출 결과 로그는 Worker 작업 결과와 함께 저장
조회성 작업은 위험도에 따라 Audit Log 여부 결정
  • Audit Log 저장 실패를 무시하면 추적성이 깨집니다.
  • 중요한 변경 작업에서는 Audit Log까지 성공해야 commit되는 구조가 안전합니다.

✅ 10. 조회 작업도 Audit Log가 필요할까?

  • 모든 조회를 Audit Log로 남기면 데이터가 너무 많아집니다.
  • 하지만 개인정보나 민감 데이터 조회는 기록이 필요할 수 있습니다.

➕ 10-1. Audit Log가 필요한 조회 후보

상담 상세 개인정보 열람
전화번호 원본 보기
엑셀 다운로드
Audit Log 조회
관리자 계정 상세 조회
권한 변경 이력 조회

➕ 10-2. Audit Log가 과한 조회

일반 상품 목록 조회
일반 상담 목록 조회
배너 목록 조회
통계 대시보드 조회

➕ 10-3. 현실적인 기준

일반 목록 조회:
Audit Log 생략 가능

개인정보 상세 열람:
필요 시 기록

파일 다운로드:
반드시 기록

전화번호 원본 복호화:
반드시 기록
  • 조회 기록은 양이 많아질 수 있으므로 신중해야 합니다.
  • 개인정보 원본을 보는 작업부터 우선 기록하는 것이 좋습니다.

✅ 11. Event Log란 무엇인가?

  • Event Log는 시스템에서 발생한 도메인 사건을 기록하는 것입니다.
  • Audit Log가 “누가 했는가”에 집중한다면, Event Log는 “무슨 일이 발생했는가”에 집중합니다.
ConsultCreated
ConsultStatusChanged
ProductPriceChanged
NotificationSent
ExportJobCompleted
WebhookReceived

➕ 11-1. Event Log가 필요한 이유

후속 작업 연결
장애 발생 시 흐름 추적
도메인 사건 기록
비동기 처리의 근거
시스템 간 데이터 동기화

➕ 11-2. Audit Log와 같이 남을 수 있음

관리자가 상담 상태 변경
  ↓
Audit Log:
관리자 A가 상태 변경

Event Log:
ConsultStatusChanged 이벤트 발생
  • 둘 중 하나만 무조건 선택하는 개념이 아닙니다.
  • 목적이 다르기 때문에 중요한 흐름에서는 함께 존재할 수 있습니다.

✅ 12. EventLog 테이블 설계

  • 초기에는 공통 EventLog 테이블로 시작할 수 있습니다.
  • 나중에 Outbox Pattern으로 확장할 수도 있습니다.
model EventLog {
  id          Int       @id @default(autoincrement())

  eventType   String
  aggregateType String
  aggregateId String

  payload     Json?
  metadata    Json?

  status      EventLogStatus @default(RECORDED)
  processedAt DateTime?
  errorCode   String?
  errorMessage String?

  requestId   String?

  occurredAt  DateTime  @default(now())
  createdAt   DateTime  @default(now())

  @@index([eventType, occurredAt])
  @@index([aggregateType, aggregateId, occurredAt])
  @@index([status, createdAt])
  @@index([requestId])
}

enum EventLogStatus {
  RECORDED
  PROCESSING
  PROCESSED
  FAILED
  IGNORED
}

➕ 12-1. 컬럼 의미

컬럼의미
eventType발생한 이벤트 종류
aggregateType대상 도메인
aggregateId대상 ID
payload이벤트 내용
metadata추가 정보
status처리 상태
occurredAt실제 발생 시각
requestId요청 추적 ID

➕ 12-2. eventType 예시

CONSULT_CREATED
CONSULT_STATUS_CHANGED
PRODUCT_PRICE_CHANGED
EXPORT_JOB_REQUESTED
EXPORT_JOB_COMPLETED
NOTIFICATION_SENT
NOTIFICATION_FAILED
WEBHOOK_RECEIVED
  • Event Log는 후속 처리와 흐름 추적에 좋습니다.
  • 다만 Audit Log와 중복이 많아질 수 있으므로 목적을 구분해야 합니다.

✅ 13. Outbox Pattern 개념

  • Outbox Pattern은 DB 변경과 이벤트 발행을 안전하게 연결하는 방식입니다.
  • DB 변경 transaction 안에서 outbox/event row를 함께 저장하고, Worker가 나중에 외부 시스템으로 이벤트를 발행합니다.
Transaction 시작
  ↓
consults.status 변경
  ↓
event_outbox row 생성
  ↓
commit
  ↓
Outbox Worker가 이벤트 처리

➕ 13-1. 왜 필요한가?

DB 변경은 성공했는데 이벤트 발행 실패
이벤트 발행은 성공했는데 DB 변경 rollback
외부 시스템과 내부 상태 불일치

➕ 13-2. Outbox 테이블 예시

model EventOutbox {
  id            Int       @id @default(autoincrement())
  eventType     String
  aggregateType String
  aggregateId   String

  payload       Json?
  status        OutboxStatus @default(PENDING)
  retryCount    Int          @default(0)
  nextRetryAt   DateTime?

  errorCode     String?
  errorMessage  String?

  createdAt     DateTime     @default(now())
  processedAt   DateTime?

  @@index([status, nextRetryAt])
  @@index([aggregateType, aggregateId, createdAt])
}

enum OutboxStatus {
  PENDING
  PROCESSING
  PROCESSED
  FAILED
}

➕ 13-3. 현재 프로젝트 기준

초기:
notification_jobs / export_jobs로 충분

확장:
외부 시스템 동기화가 많아지면 Outbox 검토

주의:
처음부터 과하게 도입하지 않기
  • Outbox Pattern은 강력하지만 복잡도가 올라갑니다.
  • 지금은 NotificationJob, ExportJob, WebhookEvent를 안정적으로 만드는 것이 우선입니다.

✅ 14. requestId와 Correlation ID

  • 운영 추적성을 높이려면 하나의 요청이 여러 로그와 DB 기록에서 연결되어야 합니다.
  • 이를 위해 requestId 또는 correlation ID를 사용합니다.
HTTP 요청
  ↓
requestId 생성
  ↓
Use Case
  ↓
Audit Log
  ↓
Event Log
  ↓
Worker Job
  ↓
Application Log

➕ 14-1. requestId 예시

req_20260830_abc123

➕ 14-2. Middleware 예시

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const requestId =
      req.headers['x-request-id']?.toString() ?? randomUUID();

    req.headers['x-request-id'] = requestId;
    res.setHeader('x-request-id', requestId);

    next();
  }
}

➕ 14-3. 활용

API 로그
Audit Log
Event Log
Job metadata
Webhook 처리 로그
에러 응답
  • requestId가 있으면 장애 분석 속도가 크게 빨라집니다.
  • “이 요청 하나가 어디까지 처리됐는지” 추적할 수 있습니다.

✅ 15. Application Log와 Audit Log를 섞지 말기

  • Application Log는 개발자/운영자가 시스템 동작을 분석하기 위한 로그입니다.
  • Audit Log는 관리자 행위를 추적하기 위한 운영 데이터입니다.
  • 둘을 같은 것으로 보면 안 됩니다.

➕ 15-1. Application Log 예시

{
  "level": "error",
  "requestId": "req_123",
  "message": "Failed to send alimtalk",
  "jobId": 10,
  "errorCode": "TIMEOUT"
}

➕ 15-2. Audit Log 예시

{
  "actorType": "ADMIN",
  "actorId": 3,
  "action": "NOTIFICATION_RESEND",
  "targetType": "NOTIFICATION_JOB",
  "targetId": "10",
  "createdAt": "2026-08-30T09:00:00.000Z"
}

➕ 15-3. 차이

Application Log:
시스템 상태/오류 분석

Audit Log:
누가 어떤 운영 작업을 했는지 기록

둘 다 필요하지만 목적이 다름
  • Application Log는 보존 기간이 짧을 수 있습니다.
  • Audit Log는 더 오래 보관해야 할 수 있습니다.

✅ 16. 개인정보와 로그

  • 로그는 개발자가 자주 놓치는 개인정보 유출 경로입니다.
  • DB에는 조심해서 저장해도 로그에 전화번호, 토큰, 상담 메모가 찍히면 의미가 없습니다.

➕ 16-1. 로그에 남기면 안 되는 것

전화번호 원본
주민등록번호/생년월일
주소
상담 메모 전체
access token
refresh token
Authorization header
cookie
DATABASE_URL
API key
Webhook secret
S3 pre-signed URL 전체

➕ 16-2. 남겨도 되는 것

requestId
jobId
consultId
productId
adminId
providerMessageId
errorCode
durationMs
status

➕ 16-3. 기준

식별자는 내부 ID 중심
개인정보는 마스킹
Secret은 절대 출력 금지
외부 API request/response 전체 로그 금지
  • 장애 분석에 필요한 정보와 민감정보를 구분해야 합니다.
  • 원본 값 대신 내부 ID와 errorCode로 추적하는 습관이 중요합니다.

✅ 17. Structured Logging

  • 운영 로그는 문자열보다 구조화된 JSON 형태가 좋습니다.
  • 검색, 필터링, 집계가 쉽기 때문입니다.

➕ 17-1. 나쁜 로그

알림톡 발송 실패함

➕ 17-2. 좋은 로그

{
  "level": "error",
  "event": "NOTIFICATION_SEND_FAILED",
  "requestId": "req_123",
  "jobId": 10,
  "targetType": "CONSULT",
  "targetId": "532",
  "provider": "ALIMTALK",
  "errorCode": "TIMEOUT",
  "retryCount": 1,
  "durationMs": 5000
}

➕ 17-3. 장점

CloudWatch에서 검색 쉬움
errorCode별 집계 가능
jobId/requestId로 추적 가능
장애 분석 속도 향상
  • 로그 메시지는 사람이 읽기 쉬워야 하지만, 기계가 검색하기 쉬워야 더 좋습니다.
  • JSON structured logging은 운영 성숙도를 높여줍니다.

✅ 18. Audit Log 조회 API

  • Audit Log는 관리자 화면에서 조회할 수 있어야 합니다.
  • 다만 권한이 있는 관리자만 접근해야 합니다.

➕ 18-1. 조회 조건

actorType
actorId
action
targetType
targetId
requestId
dateFrom
dateTo

➕ 18-2. API 예시

GET /admin/audit-logs?page=1&limit=50&action=PRODUCT_PRICE_UPDATE
GET /admin/consults/:id/audit-logs
GET /admin/products/:id/audit-logs

➕ 18-3. Repository 예시

findAdminList(
  params: {
    where: Prisma.AuditLogWhereInput;
    skip: number;
    take: number;
  },
  tx: PrismaTx = this.prisma,
) {
  return tx.auditLog.findMany({
    where: params.where,
    skip: params.skip,
    take: params.take,
    orderBy: [
      { createdAt: 'desc' },
      { id: 'desc' },
    ],
    select: {
      id: true,
      actorType: true,
      actorId: true,
      actorName: true,
      action: true,
      targetType: true,
      targetId: true,
      requestId: true,
      createdAt: true,
    },
  });
}
  • 목록에서는 before/after를 바로 보여주지 않아도 됩니다.
  • 상세를 열었을 때 필요한 경우에만 보여주는 것이 좋습니다.

✅ 19. Audit Log 권한

  • Audit Log는 민감한 기록입니다.
  • 모든 관리자가 볼 수 있게 하면 안 됩니다.

➕ 19-1. 권한 후보

AUDIT_LOG_READ
AUDIT_LOG_DETAIL_READ
AUDIT_LOG_EXPORT

➕ 19-2. 접근 기준

일반 상담 직원:
본인 작업 이력 일부만

매니저:
상담/상품 관련 이력 조회

최고 관리자:
관리자 계정/권한 변경 이력까지 조회

➕ 19-3. 주의

beforeValue/afterValue 상세 노출 제한
권한 변경 로그는 더 민감
Audit Log export는 별도 권한 필요
  • Audit Log는 운영 투명성을 위한 기록이지만, 동시에 민감 데이터가 될 수 있습니다.
  • 조회 권한도 세분화하는 것이 좋습니다.

✅ 20. Event Log와 Worker 후속 처리

  • Event Log는 후속 작업을 처리하는 근거가 될 수 있습니다.
  • 예를 들어 상담 상태가 개통 완료로 바뀌면 고객 알림, 통계 갱신, 외부 CRM 동기화가 필요할 수 있습니다.
ConsultStatusChanged 이벤트 기록
  ↓
Event Worker가 처리
  ↓
NotificationJob 생성
  ↓
Stats 갱신
  ↓
외부 CRM 동기화 Job 생성

➕ 20-1. 직접 Job 생성과 Event Log 방식 비교

방식장점단점
Use Case에서 직접 Job 생성단순하고 흐름 명확후속 작업이 늘면 Use Case가 커짐
Event Log/Outbox 기반확장성 좋음구조가 복잡해짐

➕ 20-2. 현재 기준 추천

초기:
Use Case에서 필요한 Job 직접 생성

후속 작업 증가:
EventLog 또는 Outbox로 분리

외부 시스템 동기화 증가:
Outbox Pattern 검토
  • 지금은 무리하게 이벤트 아키텍처로 갈 필요는 없습니다.
  • 다만 이벤트로 분리할 수 있는 지점을 알고 있으면 나중에 확장하기 쉽습니다.

✅ 21. 장애 분석에서의 추적 흐름

  • 운영 장애가 생기면 로그와 DB 기록을 연결해서 봐야 합니다.
  • requestId, jobId, targetId가 있으면 흐름을 따라가기 쉽습니다.

➕ 21-1. 알림톡 실패 추적 예시

상담 상세에서 알림 실패 확인
  ↓
notification_jobs에서 jobId 확인
  ↓
providerMessageId/errorCode 확인
  ↓
Worker application log에서 jobId 검색
  ↓
requestId로 상담 신청 요청 로그 확인
  ↓
필요 시 Audit Log 확인

➕ 21-2. 상품 가격 오류 추적 예시

고객이 가격 오류 제보
  ↓
product 현재 값 확인
  ↓
Audit Log에서 PRODUCT_PRICE_UPDATE 검색
  ↓
beforeValue/afterValue 확인
  ↓
actorId 확인
  ↓
requestId로 당시 API 로그 확인

➕ 21-3. 엑셀 다운로드 추적 예시

개인정보 파일 다운로드 확인 필요
  ↓
ExportJob 조회
  ↓
requestedByAdminId 확인
  ↓
Audit Log에서 EXPORT_DOWNLOAD_REQUEST 확인
  ↓
다운로드 완료 로그 확인
  ↓
fileKey/expiresAt 확인
  • 운영 추적은 단일 로그만 보는 것이 아닙니다.
  • Audit Log, Job 상태, Application Log, requestId를 연결해서 보는 것이 핵심입니다.

✅ 22. Retention: 로그 보존 정책

  • 로그와 Audit Log는 무제한 보관하면 비용과 보안 리스크가 커집니다.
  • 종류별 보존 기준을 정해야 합니다.

➕ 22-1. 보존 기준 예시

Application Log:
30~90일

Access Log:
30~90일

Audit Log:
1년 이상 또는 회사 정책 기준

Event Log:
업무 중요도에 따라 6개월~1년

Export 파일:
1~7일

NotificationJob:
운영 추적 기간 기준

➕ 22-2. 기준

장애 분석에 필요한 기간
법적/회사 정책
개인정보 포함 여부
저장 비용
조회 빈도

➕ 22-3. 주의

개인정보 포함 로그 장기 보관 금지
삭제 전 보존 정책 확인
Export 파일은 짧게 보관
Audit Log는 삭제보다 접근 제한 우선
  • Audit Log는 오래 보관할 수 있지만, 개인정보를 넣지 않는다는 전제가 필요합니다.
  • 민감정보를 넣고 오래 보관하는 구조는 위험합니다.

✅ 23. 현재 프로젝트 적용 우선순위

➕ 23-1. 1순위: 핵심 Audit Log action 정리

CONSULT_STATUS_UPDATE
CONSULT_MEMO_UPDATE
CONSULT_EXPORT_REQUEST
EXPORT_FILE_DOWNLOAD
PRODUCT_PRICE_UPDATE
PRODUCT_SOFT_DELETE
PRODUCT_RESTORE
ADMIN_ROLE_UPDATE
NOTIFICATION_RESEND

완료 기준:

위험 작업과 주요 운영 작업의 action 코드가 정의됨
권한 코드와 연결되는 작업이 정리됨
Use Case별 Audit Log 필요 여부가 정리됨

➕ 23-2. 2순위: AuditLogRepository 적용

AuditLogRepository
AuditLogFactory
sanitizeAuditValue
requestId 연결

완료 기준:

상태 변경/상품 수정/엑셀 요청에서 같은 방식으로 Audit Log 저장
민감정보 필터링 기준 적용
requestId로 추적 가능

➕ 23-3. 3순위: 관리자 Audit Log 조회 화면

Audit Log 목록
action 필터
actor 필터
target 필터
date 필터
상세 보기

완료 기준:

운영자가 누가 어떤 작업을 했는지 확인 가능
상담/상품 상세에서 관련 이력 확인 가능
권한 있는 관리자만 접근 가능

➕ 23-4. 4순위: Event Log/Outbox 검토

EventLog 테이블
ConsultStatusChanged 이벤트
NotificationJob 연동
WebhookReceived 이벤트

완료 기준:

후속 작업이 많아지는 지점 파악
Outbox 도입 필요성 판단
지금 당장 과한 구조는 피함
  • 현재 단계에서는 Event Log보다 Audit Log를 먼저 제대로 잡는 것이 우선입니다.
  • 운영자가 직접 확인해야 하는 기록부터 만드는 것이 효과가 큽니다.

✅ 24. AI/Codex에게 Audit Log 작업을 맡길 때 규칙

➕ 24-1. Codex 요청 예시

NestJS + Prisma 관리자 API에 Audit Log 구조를 추가해줘.

조건:
1. AuditLog Prisma 모델을 추가해줘
2. actorType, actorId, actorName, action, targetType, targetId, beforeValue, afterValue, metadata, requestId, ipAddress, userAgent, createdAt을 포함해줘
3. actorType+actorId+createdAt, targetType+targetId+createdAt, action+createdAt, requestId 인덱스를 추가해줘
4. AuditLogRepository를 만들어줘
5. beforeValue/afterValue에 민감정보가 들어가지 않도록 sanitizeAuditValue 유틸을 만들어줘
6. 상담 상태 변경 Use Case에서 CONSULT_STATUS_UPDATE 로그를 남겨줘
7. 상품 가격/지원금 수정 Use Case에서 PRODUCT_PRICE_UPDATE 로그를 남겨줘
8. 엑셀 Export 요청 Use Case에서 CONSULT_EXPORT_REQUEST 로그를 남겨줘
9. 엑셀 파일 다운로드 시 EXPORT_FILE_DOWNLOAD 로그를 남길 수 있게 구조를 제안해줘
10. phone, phoneNormalized, token, secret, authorization, cookie, passwordHash는 로그에 남기지 마
11. requestId를 Audit Log에 연결해줘
12. 변경 후 migration 주의사항, 테스트 케이스, QA 체크리스트를 정리해줘

➕ 24-2. 리뷰 기준

Audit Log가 중요한 변경 작업과 같은 transaction인가?
beforeValue/afterValue에 전체 row를 넣지 않았는가?
전화번호/토큰/Secret이 저장되지 않는가?
action 이름이 명확한가?
requestId가 연결되는가?
Audit Log 조회 권한이 제한되는가?
인덱스가 조회 패턴과 맞는가?
기존 API 응답이 깨지지 않는가?
  • AI/Codex에게 Audit Log를 맡길 때 가장 위험한 부분은 민감정보 저장입니다.
  • “전체 객체 저장하지 말 것”을 반드시 명시해야 합니다.

✅ 25. 실무 체크리스트

➕ 25-1. Audit Log 설계 체크리스트

  • Audit Log가 필요한 작업 목록이 정리되어 있는가?
  • action 이름이 명확한가?
  • actorType/actorId가 저장되는가?
  • targetType/targetId가 저장되는가?
  • requestId가 저장되는가?
  • beforeValue/afterValue 기준이 있는가?
  • 민감정보 필터링 기준이 있는가?
  • 조회 인덱스가 있는가?

➕ 25-2. Use Case 적용 체크리스트

  • 상담 상태 변경 시 Audit Log가 남는가?
  • 상품 가격/지원금 수정 시 Audit Log가 남는가?
  • 삭제/복구 시 Audit Log가 남는가?
  • 엑셀 Export 요청 시 Audit Log가 남는가?
  • 관리자 권한 변경 시 Audit Log가 남는가?
  • 알림톡 수동 재발송 시 Audit Log가 남는가?
  • 중요한 DB 변경과 같은 transaction으로 묶이는가?
  • Audit Log 실패 시 rollback 기준이 명확한가?

➕ 25-3. 보안 체크리스트

  • 전화번호 원본이 Audit Log에 저장되지 않는가?
  • 상담 메모 전체가 before/after에 저장되지 않는가?
  • passwordHash가 저장되지 않는가?
  • accessToken/refreshToken이 저장되지 않는가?
  • Authorization header/cookie가 저장되지 않는가?
  • DATABASE_URL/API Key/Secret이 저장되지 않는가?
  • Audit Log 조회 권한이 제한되어 있는가?
  • Audit Log export는 별도 권한이 필요한가?

➕ 25-4. 운영 추적 체크리스트

  • requestId가 API 로그와 Audit Log에 연결되는가?
  • Job ID가 Worker 로그와 DB Job에 연결되는가?
  • providerMessageId가 외부 API 결과와 연결되는가?
  • 상담/상품 상세에서 관련 Audit Log를 볼 수 있는가?
  • 장애 발생 시 requestId로 흐름을 추적할 수 있는가?
  • Structured logging을 사용하고 있는가?
  • 로그 보존 기간이 정해져 있는가?
  • 개인정보 포함 로그를 장기 보관하지 않는가?

✅ 26. AI에게 Audit Log/Event Log 설계를 물어볼 때 좋은 질문법

NestJS + Prisma + PostgreSQL 기반 온라인 휴대폰 판매몰 관리자 시스템에서 Audit Log와 Event Log를 설계하려고 해.

서비스 상황:
1. 관리자는 상담 상태 변경, 상담 메모 수정, 상품 가격/지원금 변경, 상품 삭제/복구, 엑셀 Export 요청, 알림톡 재발송, 관리자 권한 변경을 할 수 있음
2. 상담 상태 변경은 상태 이력과 Audit Log를 모두 남기고 싶음
3. 상품 가격/지원금 변경은 beforeValue/afterValue로 변경된 금액만 남기고 싶음
4. 엑셀 다운로드는 개인정보 파일과 연결되므로 요청과 다운로드를 기록하고 싶음
5. Audit Log에는 전화번호 원본, token, Secret, passwordHash, 상담 메모 전체를 저장하면 안 됨
6. requestId로 API 로그, Audit Log, Worker 로그를 연결하고 싶음
7. NotificationJob, ExportJob, WebhookEvent와도 추적 가능하게 만들고 싶음
8. Event Log 또는 Outbox Pattern은 당장 과하게 도입하지 않고 필요성을 검토하고 싶음
9. 관리자 화면에서 Audit Log 목록과 상세를 조회하고 싶음

요청:
- Audit Log와 Event Log 차이
- Audit Log 대상 작업 목록
- AuditLog Prisma schema
- action 이름 설계 기준
- beforeValue/afterValue 저장 기준
- 민감정보 sanitizer 설계
- AuditLogRepository 예시
- Use Case에서 transaction으로 Audit Log 저장하는 방식
- requestId/correlation ID 설계
- Application Log와 Audit Log 차이
- Event Log/Outbox 도입 기준
- Audit Log 조회 API와 권한 기준
- 보존 정책과 보안 체크리스트
를 실무 기준으로 정리해줘.

➕ 26-1. AI 답변 검증 기준

Audit Log와 Event Log의 목적을 구분하는가?
중요 변경 작업과 Audit Log를 같은 transaction으로 묶는가?
beforeValue/afterValue에 전체 row를 저장하지 않도록 경고하는가?
전화번호/토큰/Secret/passwordHash 저장 금지를 강조하는가?
requestId로 추적성을 연결하는가?
Application Log와 Audit Log를 섞지 않는가?
Audit Log 조회 권한을 제한하는가?
Outbox Pattern을 무조건 도입하라고 하지 않는가?
현재 프로젝트에서는 Audit Log 우선 적용을 권장하는가?

📌 요약

  • 운영 추적성은 서비스에서 어떤 일이 언제, 누구에 의해, 어떤 이유로 발생했는지 확인할 수 있는 능력입니다.
  • Audit Log는 관리자나 시스템 주체가 수행한 중요한 작업을 추적하는 기록이고, Event Log는 도메인 사건 발생을 기록하는 구조입니다.
  • 상담 상태 변경, 상품 가격/지원금 변경, 상품 삭제/복구, 엑셀 Export 요청, 알림톡 재발송, 관리자 권한 변경은 Audit Log 대상으로 우선 고려해야 합니다.
  • Audit Log에는 actorType, actorId, action, targetType, targetId, beforeValue, afterValue, requestId, ipAddress, userAgent, createdAt 같은 필드가 필요합니다.
  • action 이름은 CONSULT_STATUS_UPDATE, PRODUCT_PRICE_UPDATE, EXPORT_FILE_DOWNLOAD처럼 도메인과 행위가 드러나게 설계해야 합니다.
  • beforeValue와 afterValue에는 변경된 필드 중심으로 최소한만 저장하고, 전화번호 원본, 토큰, Secret, passwordHash, 상담 메모 전체 같은 민감정보는 넣지 않아야 합니다.
  • 중요한 DB 변경과 Audit Log 저장은 같은 transaction으로 묶어야 추적성이 깨지지 않습니다.
  • requestId를 API 로그, Audit Log, Event Log, Worker Job에 연결하면 장애 분석 시 하나의 요청 흐름을 따라가기 쉬워집니다.
  • Application Log는 시스템 오류와 동작 분석용이고, Audit Log는 운영 행위 추적용이므로 목적과 보존 기준을 구분해야 합니다.
  • Event Log와 Outbox Pattern은 후속 작업이나 외부 시스템 동기화가 많아질 때 유용하지만, 현재 단계에서는 핵심 Audit Log를 먼저 안정화하는 것이 현실적입니다.
  • Audit Log 조회 기능은 권한이 있는 관리자에게만 제공해야 하며, 상세 before/after 노출과 Export는 더 엄격하게 제한하는 것이 좋습니다.

0개의 댓글