Activity Log 모듈
학원 관리자·강사의 주요 행동을 자동으로 감사 로그(append-only)로 기록하고 커서 기반으로 조회하는 모듈입니다.
모듈 개요
| 항목 | 내용 |
|---|---|
| Gradle 서브프로젝트 | modules/activity-log |
| 베이스 패키지 | com.lumie.activitylog |
| 데이터베이스 | PostgreSQL (RLS, activity_logs 테이블) |
| 수집 방식 | ActivityLogInterceptor (Spring MVC HandlerInterceptor) |
| 외부 노출 internal-api | ActivityLogService |
주요 책임
- HTTP 요청 완료 후 자동으로 감사 로그 삽입 (
ActivityLogInterceptor) - 성공 응답(HTTP 2xx)에 한해서만 로그를 생성
- 엔티티 유형·액션 유형을 URL 패턴과 HTTP 메서드에서 자동 추론
- 커서 기반 페이지네이션으로 로그 목록 조회
ActivityLogService인터페이스를 통해 다른 모듈이 직접 로그를 삽입 가능
도메인 모델
ActivityLog 엔티티
@Entity
@Table(name = "activity_logs")
public class ActivityLog extends AppendOnlyEntity {
Long id;
Long actorId;
String actorName;
Role actorRole; // Postgres enum: actor_role_type
ActionType action; // Postgres enum: activity_action_type
EntityType entityType; // Postgres enum: activity_entity_type
Long entityId;
String entityName;
String description;
Long tenantId; // @PrePersist로 자동 주입
}
AppendOnlyEntity는 created_at만 제공합니다. 활동 로그는 한 번 삽입 후 변경되지 않으므로 updated_at이 없습니다. tenant_id는 TenantScopedEntity를 상속하지 않고 직접 선언하여 불필요한 updated_at 컬럼 추 가를 피합니다.
ActionType (Postgres enum)
| 값 | 매핑 |
|---|---|
CREATE | POST (일반) |
UPDATE | PUT / PATCH |
DELETE | DELETE |
VIEW | GET |
DEACTIVATE | POST */deactivate |
REACTIVATE | POST */reactivate |
TERMINATE | POST */terminate |
CHECK_IN | POST */check-in |
SEND | POST */send* |
CLOSE | POST */close |
EntityType (Postgres enum)
인터셉터가 URL 패턴으로 자동 매핑하는 엔티티 유형입니다.
| URL 패턴 | EntityType |
|---|---|
/v1/classes/* | CLASS |
/v1/classes/*/enrollments/** | ENROLLMENT |
/v1/students/** | STUDENT |
/v1/lectures/** | LECTURE |
/v1/assignments/** | ASSIGNMENT |
/v1/exams/** | EXAM |
/v1/sms/** | SMS |
/v1/attendance/** | ATTENDANCE |
/v1/files/** | FILE |
| 기타 다수 | (인터셉터 PATH_TO_ENTITY 맵 참조) |
자동 로깅 메커니즘
ActivityLogInterceptor는 Spring MVC HandlerInterceptor로 등록되어 afterCompletion 시점에 실행됩니다.
HTTP 요청 완료
↓
ActivityLogInterceptor.afterCompletion()
├─ ActivityLogContext.isLogged() → 이미 로깅됨이면 스킵
├─ UserId 없으면 스킵 (미인증 요청)
├─ HTTP 4xx/5xx이면 스킵
├─ URL 패턴으로 EntityType 결정
├─ HTTP 메 서드 + URL suffix로 ActionType 결정
└─ ActivityLogService.log() 호출 → activity_logs INSERT
다른 모듈에서 세밀한 제어가 필요한 경우 ActivityLogContext.markLogged()로 인터셉터 자동 삽입을 억제하고 ActivityLogService를 직접 주입해 로그를 남길 수 있습니다.
REST API
기본 경로: /v1/activity-logs
| 메서드 | 경로 | 설명 |
|---|---|---|
GET | /v1/activity-logs | 활동 로그 조회 (커서 기반 페이지네이션) |
쿼리 파라미터
| 파라미터 | 설명 | 기본값 |
|---|---|---|
actorId | 특정 사용자 행동만 필터 | - |
entityType | 엔티티 유형 필터 | - |
startDate | 시작일 (ISO 날짜) | - |
endDate | 종료일 (ISO 날짜) | - |
search | 엔티티 이름 등 키워드 검색 | - |
cursor | 다음 페이지 커서 | - |
limit | 페이지 크기 (최대 100) | 30 |
응답 타입: CursorResponse<ActivityLogResponse>
내부 API (libs/internal-api)
public interface ActivityLogService {
void log(Long actorId, String actorName, String actorRole,
String action, String entityType, Long entityId,
String entityName, String description);
}
ActivityLogServiceAdapter(adapter/in/internal/)가 이를 구현합니다. 다른 모듈에서 인터셉터가 자동 포착하지 못하는 비즈니스 이벤트를 직접 기록할 때 사용합니다.
멀티테넌시
activity_logs 테이블에 tenant_id BIGINT NOT NULL이 있으며 RLS 정책이 적용됩니다. @PrePersist에서 TenantContextHolder.getRequiredTenantId()로 자동 주입됩니다.
관련 문서
- Backend Architecture — 헥사고날 아키텍처
- Multi-tenancy — 멀티테넌시 구조