728x90
1. 도입: 단순 로그를 넘어서
서비스에서 유저가 어떤 행동을 했는지를 기록하는 건 단순한 기능 같지만, 실제로는 시스템의 인사이트와 회복력을 좌우하는 핵심 관측 지표가 된다. 예를 들어 다음과 같은 시나리오를 상상해보자.
- 어떤 유저가 로그인에 실패한 후 탈퇴 페이지로 이동한다.
- 게시글을 클릭했는데 500 에러가 발생한다.
- 예약 시스템에서 요청이 들어왔으나 Kafka에 전송되지 않았다.
이런 상황을 파악하기 위해선 단순한 “누가 무엇을 했다” 수준의 로그로는 부족하다. 우리는 유저의 행동을 시간, 플랫폼, 처리 결과 등 **맥락(Context)**과 함께 수집해야 한다.
2. 로그 시스템 설계 목표
이번 로그 수집 시스템은 다음의 목표를 달성하고자 설계되었다:
| 항목 | 설명 |
|---|---|
| 표준화 | 로그 데이터에 공통적으로 포함되어야 할 필드를 정의 |
| 확장성 | 행동마다 필요한 상세 정보를 유연하게 수용 |
| 전송 안전성 | Outbox 패턴을 활용해 Kafka 전송의 신뢰성 확보 |
| 구조적 분리 | BFF, 도메인 로직과 독립된 이벤트 설계 유지 |
3. 로그 메타데이터 구조 설계
모든 로그는 기본적인 맥락 정보를 가져야 한다. 이를 위해 LogMetadata 객체를 설계했다:
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LogMetadata {
private String userAgent;
private String ip;
private String referer;
private String source; // "WEB", "MOBILE" 등
private long durationMillis; // 처리 시간 (ms)
private boolean success; // 처리 결과
}
이 구조는 BFF에서 쉽게 추출할 수 있는 정보를 담아 유저의 환경과 요청 결과를 파악하는 데 도움을 준다.
4. 로그 이벤트 구조
행위 단위의 로그는 LogEvent 클래스로 정의한다. 이 클래스는 CustomEvent를 구현해 Kafka 전송 및 Outbox 저장에 사용된다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LogEvent implements CustomEvent {
private Long userId;
private LogActionType actionType;
private LocalDateTime timestamp;
private LogMetadata metadata;
private Object payload; // 행동마다 다른 정보 (선택 사항)
@Override
public String name() {
return actionType.name();
}
@Override
public String getTopic() {
return LogTopic.ACTIVITY_LOG;
}
}
payload: 자유로운 데이터 확장성을 위해Object타입 사용. Jackson 직렬화 전제하에 Map, DTO 모두 수용 가능.
5. 로그 행동 타입과 토픽
public enum LogActionType {
LOGIN, LOGOUT, VIEW_PAGE, RESERVE, CANCEL, UPDATE_PROFILE
}
public class LogTopic {
public static final String ACTIVITY_LOG = "activity.log.save";
}
- 로그 종류마다 actionType으로 구분되고, Kafka 전송 시에는 고정된 토픽
activity.log.save로 발송된다.
6. 사용 흐름 예시
예: 유저 로그인 성공 시
LogEvent loginEvent = LogEvent.builder()
.userId(42L)
.actionType(LogActionType.LOGIN)
.timestamp(LocalDateTime.now())
.metadata(LogMetadata.builder()
.userAgent(request.getHeader("User-Agent"))
.ip(request.getRemoteAddr())
.referer(request.getHeader("Referer"))
.source("WEB")
.durationMillis(120)
.success(true)
.build())
.payload(Map.of("method", "email"))
.build();
Outbox 발행
outboxEventPublisher.publish(loginEvent);
→ 이 이벤트는 OutboxEventEntity로 직렬화되어 저장되고, 이후 스케줄러를 통해 Kafka에 안전하게 전송된다.
7. 장점 및 설계 당위성
구조적 강점
| 설계 요소 | 효과 |
|---|---|
LogMetadata 별도 구성 |
모든 로그에 공통 정보 통합 |
Object payload |
다양한 로그 시나리오 수용 (e.g. 게시글 ID, 실패 이유 등) |
CustomEvent 인터페이스 통합 |
Outbox 패턴과 일관성 유지 |
| Kafka 토픽 자동 지정 | 서비스별 메시지 라우팅 간소화 |
확장성 고려
- 향후 행동별
payloadDTO 정의 가능 - Slack 연동, Dead Letter 처리 등도 구조적으로 대응 가능
8. 회고: 의미 있는 로그란?
이 시스템은 단순한 “이벤트 전송”을 넘어서, 서비스에서 발생한 일을 다각도로 관찰할 수 있는 기반을 만든다.
특히 다음과 같은 효과를 기대할 수 있다:
- 모니터링: 특정 이벤트 누락이나 지연 발생 시 추적 가능
- 분석: 행동별 유저 사용 패턴 파악
- 복구성 향상: Kafka 장애에도 Outbox로 인해 로그 손실 방지
필요하면 위 글을 .md 포맷으로도 정리해줄 수 있어. 구조나 방향이 마음에 들면 이어서 2편도 정리해줄게.
728x90
'BindProject' 카테고리의 다른 글
| [Backend] 유저 활동 로그 수집 모듈 개발기 5편 : 마무리 회고 (0) | 2025.06.22 |
|---|---|
| [Backend] 유저 활동 로그 수집 모듈 개발기 4편 : 로그 수집 서비스 구현 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 2-3편 : Outbox 설계 이후: 테스트 전략과 실제 구현 결과 (0) | 2025.06.21 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 2-2편 :Kafka 전송 최적화를 위한 배치 전송 구조 개선기 (0) | 2025.06.21 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 2-1편 : Kafka Outbox 패턴 기반 유저 활동 로그 수집기 리팩토링기(문제 확인) (0) | 2025.06.21 |