Skip to content

feat: 행동 로그를 analytics_events 테이블에 적재 + 유입 전환율 추적#235

Merged
Junhyukkkk merged 5 commits into
developfrom
feature/analytics-events-db
Jun 17, 2026
Merged

feat: 행동 로그를 analytics_events 테이블에 적재 + 유입 전환율 추적#235
Junhyukkkk merged 5 commits into
developfrom
feature/analytics-events-db

Conversation

@Junhyukkkk

@Junhyukkkk Junhyukkkk commented Jun 14, 2026

Copy link
Copy Markdown
Member

📌 관련 이슈

  • closes #

🔍 작업 내용

행동 로그(analytics event)를 stdout 로그가 아니라 RDB(analytics_events) 테이블에 직접 INSERT하도록 바꿨습니다. 이제 별도 로그 수집기 없이 SQL로 바로 집계/조회할 수 있습니다. 또한 UTM 유입 클릭(landing_visited)을 적재해 가입 전환율까지 계산할 수 있게 했습니다.

📝 변경 사항

  • 테이블/엔티티 추가
    • V15__add_analytics_events.sqlanalytics_events 테이블(append-only, FK 없음, (event, occurred_at)·(occurred_at) 인덱스)
    • AnalyticsEventRecord — 매핑 엔티티. 공통 필드는 컬럼, 이벤트별 가변 속성은 properties(JSON 문자열) 컬럼
    • AnalyticsEventRepositoryJpaRepository
  • 로거 전환: AnalyticsEventLogger가 JSON 로그 출력 대신 analytics_events에 INSERT. 적재 실패는 그대로 삼켜 비즈니스 로직에 영향 없음
  • 유입 추적: TrackingController/api/track/visit 호출 시 landing_visited 이벤트 1건 적재 (전환율 분모)
  • 통합테스트: AnalyticsEventLoggerIntegrationTest — 실제 PostgreSQL(Testcontainers)로 적재/properties 직렬화/회원여부 검증 (3건)

💬 리뷰어에게

  • properties 컬럼 타입: 로컬 H2(PostgreSQL 모드)와 운영 PostgreSQL 양쪽 호환을 위해 jsonb가 아닌 **TEXT(JSON 문자열)**로 저장합니다. PostgreSQL에선 (properties::jsonb)->>'utm_campaign'으로 캐스팅해 쿼리 가능합니다.
  • 적재 트랜잭션: 호출한 비즈니스 트랜잭션에 함께 묶여 INSERT됩니다(단순 우선, 커넥션 추가 점유 없음). 비즈니스 트랜잭션이 롤백되면 그 로그 행도 롤백됩니다. 완전 격리(롤백돼도 로그 보존)가 필요하면 @Async 분리가 후속 과제입니다.
  • 검증 완료: Testcontainers 실 PostgreSQL에서 V15 마이그레이션 적용 + 엔티티 매핑(ddl-auto: validate) + 적재 테스트 3건 통과 확인했습니다.
  • 프론트 의존(전환율): landing_visited는 프론트가 랜딩 시 /api/track/visitcredentials:'include'로 호출해줘야 쌓입니다(기존 UTM 추적과 동일 전제).

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 방문 이벤트 추적 고도화: /api/track/visit 호출 시 UTM 정보를 바탕으로 landing_visited 분석 이벤트가 기록됩니다.
    • 분석 이벤트 영속화: 회원/비회원 구분 및 공통 식별자(anonymous 포함), 발생 시각, 이벤트 속성(JSON)이 RDB의 이벤트 테이블에 저장됩니다. 속성이 없으면 properties가 비워집니다.
    • 이벤트 저장 테이블 및 인덱스 추가로 기간 기반 조회를 지원합니다.
  • Tests

    • 회원/비회원 및 속성 유무에 대한 분석 이벤트 통합 테스트를 추가했습니다.

@Junhyukkkk Junhyukkkk self-assigned this Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00e218bb-8348-4eed-b02b-db170fe753b6

📥 Commits

Reviewing files that changed from the base of the PR and between fe6aba6 and 1e7d4e6.

📒 Files selected for processing (1)
  • src/integrationTest/java/com/ject/vs/support/BaseIntegrationTest.java

Walkthrough

AnalyticsEventLogger의 이벤트 기록 방식이 JSON 로그 출력에서 analytics_events RDB 테이블 직접 적재로 전환된다. Flyway 마이그레이션으로 테이블과 인덱스를 추가하고, 신규 JPA 엔티티·리포지토리를 도입한다. TrackingController에 방문 이벤트 로깅이 연결되며, Testcontainers 인프라 업데이트를 거쳐 통합 테스트 3종이 추가된다.

Changes

Analytics 이벤트 DB 적재 전환

Layer / File(s) Summary
analytics_events 테이블 스키마 및 JPA 엔티티/리포지토리
src/main/resources/db/migration/V15__add_analytics_events.sql, src/main/java/com/ject/vs/analytics/AnalyticsEventRecord.java, src/main/java/com/ject/vs/analytics/AnalyticsEventRepository.java
Flyway 마이그레이션으로 analytics_events 테이블(공통 컬럼 + properties text)과 인덱스 2종을 생성하고, 해당 테이블에 매핑되는 AnalyticsEventRecord JPA 엔티티와 AnalyticsEventRepository 인터페이스를 추가한다.
AnalyticsEventLogger RDB 적재 로직 변경
src/main/java/com/ject/vs/analytics/AnalyticsEventLogger.java
AnalyticsEventRepository 필드를 추가하고, log() 내부에서 LinkedHashMap payload 구성 및 log.info() 호출을 제거한다. event.properties()를 JSON 직렬화(빈 값이면 null)해 analyticsEventRepository.save(new AnalyticsEventRecord(...)) 호출로 대체하며, Javadoc도 갱신된다.
TrackingController landing_visited 이벤트 연동
src/main/java/com/ject/vs/analytics/TrackingController.java
AnalyticsEventLogger 의존성을 추가하고, /api/track/visit 엔드포인트가 first-touch 쿠키 기록 후 UTM 파라미터를 포함한 landing_visited 이벤트를 analytics.log()로 기록하도록 변경된다.
AnalyticsEventLogger 통합 테스트 및 테스트 인프라 업데이트
src/integrationTest/java/com/ject/vs/support/BaseIntegrationTest.java, src/integrationTest/java/com/ject/vs/analytics/AnalyticsEventLoggerIntegrationTest.java
BaseIntegrationTest에서 Testcontainers 컨테이너 라이프사이클을 JUnit 관리에서 정적 초기화 블록 싱글톤 패턴으로 변경하고, 실제 PostgreSQL 연동 통합 테스트 3종(비회원/회원/속성 없음 케이스)을 추가한다.

Sequence Diagram

sequenceDiagram
  participant Client
  participant TrackingController
  participant AnalyticsEventLogger
  participant AnalyticsEventRepository
  participant analytics_events_DB

  Client->>TrackingController: GET /api/track/visit?utm_source=...
  TrackingController->>TrackingController: UtmAttribution.of() 생성
  TrackingController->>TrackingController: utmCookie.writeFirstTouch()
  TrackingController->>AnalyticsEventLogger: log(landing_visited 이벤트 + UTM 속성)
  AnalyticsEventLogger->>AnalyticsEventLogger: HTTP 컨텍스트에서 userId/platform 추출
  AnalyticsEventLogger->>AnalyticsEventLogger: properties JSON 직렬화
  AnalyticsEventLogger->>AnalyticsEventRepository: save(AnalyticsEventRecord)
  AnalyticsEventRepository->>analytics_events_DB: INSERT 1건
  TrackingController->>Client: 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • JECT-Study/JECT2-4th-Server#233: 메인 PR의 AnalyticsEventLogger 동작 변경(RDB 적재)과 TrackingController의 이벤트 로깅 추가는 PR #233의 AnalyticsEventLogger/TrackingController 기반 애널리틱스 인프라를 직접 확장한 변경이다.

Suggested reviewers

  • tlarbals824
  • KII1ua

Poem

🐇 로그 대신 DB에 쏙!
analytics_events 테이블, 토끼가 파놓은 굴~
UTM도 properties도 JSON으로 담아
비회원 회원 모두 한 줄씩 적재!
이제 이벤트는 사라지지 않아요 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 PR의 주요 변경사항을 명확하게 요약하고 있습니다. 행동 로그를 analytics_events 테이블에 적재한다는 핵심 변경과 유입 전환율 추적 기능을 간결하게 표현합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/analytics-events-db

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/ject/vs/analytics/TrackingController.java (1)

31-46: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

UTM 입력 길이 상한 검증이 없어 DB/로그 적재 경로가 과도하게 커질 수 있습니다.

/api/track/visit는 외부 입력(utm_*)을 길이 제한 없이 받아 쿠키/analytics 저장 경로로 전달합니다. 비정상적으로 긴 쿼리스트링이 반복 유입되면 properties(TEXT) 적재량이 급증해 저장소/처리량에 영향을 줄 수 있습니다. 컨트롤러에서 필드별 최대 길이(예: 100~255) 제한 또는 절단을 적용해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/ject/vs/analytics/TrackingController.java` around lines 31
- 46, The endpoint is accepting UTM parameters (source, medium, campaign,
content) without any length validation, which can lead to excessively large data
being stored in cookies and analytics logs. Add validation logic before the
UtmAttribution.of() call to enforce maximum length limits on each utm_*
parameter (e.g., 100-255 characters). Either reject requests that exceed these
limits or truncate the values to the maximum allowed length before passing them
to utmCookie.writeFirstTouch() and analytics.log().
🧹 Nitpick comments (1)
src/main/resources/db/migration/V15__add_analytics_events.sql (1)

12-12: ⚡ Quick win

TIMESTAMPTIMESTAMP WITH TIME ZONE으로 변경 검토 필요.

Instant는 UTC 기반이지만 PostgreSQL의 TIMESTAMP(without time zone)는 타임존 정보 없이 저장됩니다. JDBC 드라이버/Hibernate 설정에 따라 저장·조회 시 암묵적 변환이 발생해 혼란을 초래할 수 있습니다.

TIMESTAMPTZ를 사용하면 명시적으로 UTC로 저장되어 직접 DB 쿼리 시에도 일관성이 유지됩니다.

🔧 권장 수정
-    occurred_at  TIMESTAMP NOT NULL,
+    occurred_at  TIMESTAMPTZ NOT NULL,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/db/migration/V15__add_analytics_events.sql` at line 12,
The occurred_at column is defined as TIMESTAMP without timezone information, but
the Java code uses Instant which is UTC-based. This mismatch can cause implicit
conversion issues depending on JDBC driver and Hibernate settings. Change the
occurred_at column definition from TIMESTAMP to TIMESTAMP WITH TIME ZONE (or
TIMESTAMPTZ) to explicitly store UTC values and ensure consistency across all
database interactions and queries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/integrationTest/java/com/ject/vs/analytics/AnalyticsEventLoggerIntegrationTest.java`:
- Line 50: The code directly accesses findAll().get(0) without first validating
that results were returned, which reduces test reliability by failing to
distinguish between data load failures and empty results. At both line 50 and
line 60 in the AnalyticsEventLoggerIntegrationTest class, add an assertion to
verify the list has the expected size using hasSize(1) or singleElement() before
accessing the element at index 0, ensuring clear test failure messages when data
is not properly loaded or unexpectedly present.

---

Outside diff comments:
In `@src/main/java/com/ject/vs/analytics/TrackingController.java`:
- Around line 31-46: The endpoint is accepting UTM parameters (source, medium,
campaign, content) without any length validation, which can lead to excessively
large data being stored in cookies and analytics logs. Add validation logic
before the UtmAttribution.of() call to enforce maximum length limits on each
utm_* parameter (e.g., 100-255 characters). Either reject requests that exceed
these limits or truncate the values to the maximum allowed length before passing
them to utmCookie.writeFirstTouch() and analytics.log().

---

Nitpick comments:
In `@src/main/resources/db/migration/V15__add_analytics_events.sql`:
- Line 12: The occurred_at column is defined as TIMESTAMP without timezone
information, but the Java code uses Instant which is UTC-based. This mismatch
can cause implicit conversion issues depending on JDBC driver and Hibernate
settings. Change the occurred_at column definition from TIMESTAMP to TIMESTAMP
WITH TIME ZONE (or TIMESTAMPTZ) to explicitly store UTC values and ensure
consistency across all database interactions and queries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcffd372-d3a6-44a8-898d-ee576e6e5ea4

📥 Commits

Reviewing files that changed from the base of the PR and between b34abb7 and fe6aba6.

📒 Files selected for processing (6)
  • src/integrationTest/java/com/ject/vs/analytics/AnalyticsEventLoggerIntegrationTest.java
  • src/main/java/com/ject/vs/analytics/AnalyticsEventLogger.java
  • src/main/java/com/ject/vs/analytics/AnalyticsEventRecord.java
  • src/main/java/com/ject/vs/analytics/AnalyticsEventRepository.java
  • src/main/java/com/ject/vs/analytics/TrackingController.java
  • src/main/resources/db/migration/V15__add_analytics_events.sql

.userId(1024L)
.put("method", "kakao"));

AnalyticsEventRecord row = analyticsEventRepository.findAll().get(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

행 수 검증 없이 findAll().get(0)에 직접 접근하면 테스트 신뢰성이 떨어집니다.

Line 50, Line 60에서 먼저 hasSize(1)(또는 singleElement())를 검증한 뒤 행을 꺼내야, 적재 실패와 데이터 오염을 명확히 구분할 수 있습니다.

제안 수정안
-        AnalyticsEventRecord row = analyticsEventRepository.findAll().get(0);
+        List<AnalyticsEventRecord> rows = analyticsEventRepository.findAll();
+        assertThat(rows).hasSize(1);
+        AnalyticsEventRecord row = rows.get(0);
         assertThat(row.getUserId()).isEqualTo(1024L);
         assertThat(row.isMember()).isTrue();
         assertThat(row.getProperties()).contains("\"method\":\"kakao\"");
@@
-        AnalyticsEventRecord row = analyticsEventRepository.findAll().get(0);
+        List<AnalyticsEventRecord> rows = analyticsEventRepository.findAll();
+        assertThat(rows).hasSize(1);
+        AnalyticsEventRecord row = rows.get(0);
         assertThat(row.getEvent()).isEqualTo("simple_event");
         assertThat(row.getProperties()).isNull();

Also applies to: 60-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/integrationTest/java/com/ject/vs/analytics/AnalyticsEventLoggerIntegrationTest.java`
at line 50, The code directly accesses findAll().get(0) without first validating
that results were returned, which reduces test reliability by failing to
distinguish between data load failures and empty results. At both line 50 and
line 60 in the AnalyticsEventLoggerIntegrationTest class, add an assertion to
verify the list has the expected size using hasSize(1) or singleElement() before
accessing the element at index 0, ensuring clear test failure messages when data
is not properly loaded or unexpectedly present.

@github-actions

Copy link
Copy Markdown

빌드 실패
코드를 확인해주세요.

@github-actions

Copy link
Copy Markdown

빌드 실패
코드를 확인해주세요.

@github-actions

Copy link
Copy Markdown

빌드 성공
배포 준비 완료!

@Junhyukkkk Junhyukkkk merged commit 9a7fa9a into develop Jun 17, 2026
3 checks passed
@Junhyukkkk Junhyukkkk deleted the feature/analytics-events-db branch June 17, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant