블로그 로그 시스템 구축하기 - 클라이언트 사이드 로깅 시스템 구현
이전 글에서 서버 사이드 로그만으로는 Next.js 애플리케이션에서 사용자의 실제 행동을 추적할 수 없다는 것을 깨달았다. 이번 글에서는 클라이언트 사이드 로깅 시스템을 직접 구현한 과정을 정리한다.
수집 목표 정의하기
클라이언트에서 수집하고자 하는 데이터를 명확히 정의했다.
핵심 수집 항목
- Referrer / UTM Parameter: 사용자가 어디서 유입됐는지 추적
- 특정 포스트 조회수: 어떤 글이 인기 있는지 파악
- 세션 유지 시간: 사용자가 얼마나 오래 머물렀는지
- 커스텀 이벤트: 특정 인터랙션 발생 시 기록
구현 계획 수립
각 수집 항목별로 구현 방법을 정리했다.
포스트 조회수
- 사용자의 Route 변화를 감시
- Route 이동 시 이벤트 발생
세션 유지 시간
- 기존에 구현해둔 3000ms threshold 이벤트 활용
- threshold를 좀 더 낮춰서 민감도 조절
커스텀 이벤트
- 기존
logger클래스를 활용 - 사용자 구분을 위해
trace_id를 클라이언트에서 부여
Referrer / UTM Parameter
- 첫 진입 시 URL의
utm파라미터 파싱 document.referrer로 이전 페이지 확인 가능- 단, HTTPS→HTTP 이동이나
Referrer-Policy헤더에 따라 제한될 수 있음
- 단, HTTPS→HTTP 이동이나
구현 방식
Analytics 컴포넌트 구조
layout 컴포넌트에 Analytics 컴포넌트를 삽입하여 사용자의 Route 변화를 추적한다. 모든 페이지는 layout 컴포넌트의 children으로 렌더링되기 때문에 전역적으로 추적이 가능하다.
trace_id 관리 방식
trace_id를 어떻게 관리할지 고민이 필요했다.
선택지:
- 클라이언트 상태로 관리 → 새로고침 시 새로운 ID 부여됨
- 쿠키 → 추가 권한 필요할 수 있음
- 세션 스토리지 → 탭 단위로 유지, 권한 불필요
결정: 세션 스토리지 사용
세션 스토리지를 선택한 이유:
- 같은 탭에서는 일관된 ID 유지
- 별도의 쿠키 동의 팝업 불필요
- 탭을 닫으면 자동 삭제되어 프라이버시 친화적
세션 유지 시간 측정 시 고려사항
document.visibilityState API를 활용해 탭이 비활성화되면 측정을 중단하도록 구현할 수 있다. 하지만 현재 구현에서는 유저 이벤트 발생 시에만 로그를 수집하기 때문에, 비활성 상태 체크를 추가로 적용하지 않았다.
페이지 종료 시 데이터 전송
최종 세션 시간을 전송하기 위해 beforeunload 이벤트에서 navigator.sendBeacon()을 사용한다. 일반 fetch는 페이지 종료 시 취소될 수 있지만, sendBeacon은 안정적으로 전송된다.
실제 구현 구조

모듈 분리
기존 unified-logger에서 클라이언트 사이드 로깅을 위해 user-analytics를 분리했다.
unified-logger (서버 사이드)
└── 기존 로깅 로직
user-analytics (클라이언트 사이드)
├── 배치 전송 로직
├── 세션 ID 관리
├── beforeunload 처리
└── 이벤트 큐 관리
API 분리
서버 로그용 /api/logs와 별도로 클라이언트 분석용 /api/analytics 엔드포인트를 만들었다.
user-analytics 핵심 기능
- 배치 전송: 이벤트 발생 시마다 개별 요청을 보내지 않고, 일정 시간마다 큐에 쌓인 이벤트를 한 번에 전송
- 세션 ID 관리:
sessionStorage로 유저의 세션 ID 관리 - beforeunload 처리: 페이지 종료 시 남은 이벤트 전송
- setTimeout 트리거: 유저 이벤트 발생 시 타이머 시작, 일정 시간 후 배치 전송
구현 시 고려한 점들
네트워크 효율성
- 개별 요청 대신 배치 처리로 네트워크 부하 감소
- 네트워크 실패 시 재시도 로직 또는 로컬 버퍼링 고려
프라이버시
- IP 수집 시 익명화 처리 방안 (마지막 옥텟 제거 등)
- 쿠키 동의 팝업이 뜨지 않도록 세션 스토리지 사용
데이터 신뢰성
beforeunload에서sendBeacon사용으로 안정적 전송- 탭 비활성화 시 불필요한 데이터 수집 방지
Grafana 대시보드 구성
수집된 데이터를 시각화하기 위해 Grafana 대시보드를 구성했다. JSON 파일로 대시보드 설정을 관리하면 버전 관리도 가능하다.
대시보드 파일은 Claude Code를 통해 생성했다.
서버 로그와 클라이언트 로그의 역할 분리
최종적으로 정리한 로그 시스템의 역할 분담:
| 구분 | 서버 로그 (Nginx, Middleware) | 클라이언트 로그 (Analytics) |
|---|---|---|
| 목적 | 인프라 모니터링 | 사용자 행동 분석 |
| 수집 내용 | HTTP 상태 코드, 응답 시간, 에러 | 페이지뷰, 세션 시간, 커스텀 이벤트 |
| 특징 | 모든 요청 기록 (봇 포함 가능) | 실제 사용자 행동만 기록 |
배운 점
-
SPA 환경에서의 로깅은 다르다: 전통적인 서버 로그 방식으로는 SPA의 클라이언트 라우팅을 추적할 수 없다.
-
목적에 맞는 계층에서 데이터를 수집하자: 서버 모니터링은 서버에서, 사용자 분석은 클라이언트에서.
-
프라이버시를 고려한 설계: 쿠키 동의 없이도 기본적인 분석이 가능하도록 세션 스토리지를 활용했다.
-
배치 처리의 중요성: 이벤트마다 네트워크 요청을 보내면 성능에 악영향을 준다. 배치 처리로 효율성을 높일 수 있다.