Sentry를 활용한 프론트엔드 에러 모니터링
Sentry를 활용해서 프론트엔드의 에러를 모니터링해보자
모니터링 툴을 도입하게된 배경
저는 물류센터 운영을 위한 WMS(Warehouse Management System)를 개발하고 있습니다.
WMS는 일반적인 커머스 서비스와 달리 입고, 적치, 피킹, 패킹, 출고와 같은 실제 현장 작업을 지원하는 시스템입니다.
특히 물류센터에서는 네트워크 환경이 불안정하거나 성능이 낮은 PC를 사용하는 경우가 많고, 작업자들이 여러 탭을 동시에 사용하거나 반복적으로 새로고침하는 상황도 자주 발생합니다.
따라서 단순한 UI 오류보다 업무 흐름이 중단되는 장애를 빠르게 발견하고 원인을 파악하는 것이 매우 중요합니다.
예를 들어서 “입고 진행이 안됩니다.” 라는 온콜 요청이 들어오는 경우가 있습니다.
- 어떤 화면에서 발생했는지?
- 에러메시지가 무엇으로 뜨는지?
- 입고 번호는 무엇인지?
이런 환경에서 장애 대응 과정의 개발자 경험을 개선하기 위해 프론트엔드 모니터링 도구를 도입하게 되었습니다.
어떤 에러를 수집해야할까?
Sentry를 도입한 이후 가장 먼저 고민이 됐던건 “어떤 에러를 수집해야할까?” 였습니다.

화면 영역
- 서버사이드 렌더링 시 Hydration Error
- React Rendering Error
- TypeError, ReferenceError, NotFoundError
→ 주로 코드 실행 과정에서 발생하는 프론트엔드 런타임 에러들입니다.
데이터 영역
- 화면 상태와 서버 상태 불일치
- 잘못된 파라미터 전송
- Stale Data
- 중복 요청으로 인한 동시성 이슈
→ 주로 비즈니스 로직이나 상태 관리 과정에서 발생하는 문제들입니다.
외부 요인에서의 장애
- 네트워크 장애
- API 장애
- 서버 타임아웃
- 브라우저 환경 문제
- S3, CDN .. .등등
→ 프론트엔드 외부 환경으로 발생하는 문제들입니다.
실제로 운영 과정에서 발생했던 온콜들을 다시 살펴보니 대부분 데이터 영역에서의 에러였습니다.
- 실제 상태와 서버 상태가 불일치 하는 경우
- 사용자의 중복 요청으로 인해 상태가 꼬이는 경우
- 특정 작업이 가능한 상태가 아닌데 요청이 발생하는 경우
그래서 “물류센터 환경은 열악한 네트워크 환경, 성능이 낮은 PC, 작업자들 휴먼에러(새로고침, 여러 탭 띄워놓고 동시 작업 등)가 많을것이다.” 라는 가설을 세우고 데이터 영역에서 발생하는 비즈니스 에러를 중점적으로 관측하기로 결정했습니다.
3. 어떻게 에러를 수집할까?
Sentry에서는 기본적으로 2가지 이벤트 전송 API를 제공합니다.
captureException는 에러 객체나 문자열을 전송
try {
await api.get('/users');
} catch (error) {
Sentry.captureException(error); // 실제 Error 객체를 Sentry로 전송
}captureMessage는 문자열만 전송
if (!user) {
Sentry.captureMessage('User not found'); // 문자열만 Sentry로 전송
}처음에는captureException 과 captureMessage 를 활용하여 주요 예외 상황을 수집(로깅)하는 수준이었는데 좀 더 Sentry를 깊게 들여다 보니 다양한 기능들을 제공하고 있었습니다.
이벤트에 여러 종류의 정보를 붙이기
Tag
장애를 빠르게 찾기 위한 정보입니다. (검색용)
endpoint
api_version
domain_action
url
error.code
Extra(Additional Data)
장애를 분석하기 위한 추가 정보입니다.
request_body
response_code
response_message
request_id
Context
장애가 발생한 업무 맥락입니다.
단순한 API 에러가 아니라 어떤 업무 과정에서 발생한 문제인지 함께 수집할 수 있습니다.

Breadcrumb
에러 직전 사용자의 행동 흐름입니다.
어떤 과정을 거쳐서 에러가 발생했는지 확인할 수 있습니다.

Level
이벤트의 중요도를 구분합니다.
error // 실제 심각한 장애
warning // FE에서 발생하는 경고나, 간단한 비즈니스 에러
debug // 분석용 이벤트
Fingerprint
동일한 유형의 장애를 하나의 이슈로 그룹핑 하기 위한 기능입니다.
예를들어 송장 출력 API에 장애가 하루에 수십번 발생하더라도 하나의 Issue 그룹에 묶어서 관리할 수 있습니다.

Scope를 이용하여 하나의 이벤트로 전송
Sentry에서는 이러한 정보들을 모아 Scope라는 단위로 묶어 하나의 이벤트에 함께 전송할 수 있습니다.
Sentry.withScope((scope) => {
scope.setTag("endpoint", "/outbound/generate-invoice");
scope.setTag("api_version", "v1");
scope.setTag("domain_action", "송장출력");
scope.setTag("capture_mode", "top20");
scope.setTag("error.code", "20015");
scope.setExtra("request_body", {
outboundId: 12345,
warehouseId: 10,
});
scope.setExtra("response_code", "20015");
scope.setExtra("response_message", "송장 출력에 실패했습니다.");
scope.setExtra("request_id", "req-abc-123");
scope.setLevel("error");
scope.setFingerprint(["송장출력", "20015"]);
Sentry.captureMessage("매우매우 심각한 에러 발생!!!!", "error");
});Session Replay
Session Replay는 사용자의 화면과 행동 흐름을 기록하는 유용한 기능입니다. 에러 직전의 상황이 녹화가 되어서 Sentry에서 확인할 수 있습니다.

초기 도입 시 발생한 문제
에러의 종류와 수가 너무 많아 관리가 어려웠습니다.
실시간으로 쌓이는 알림 속에는 해결해야 할 중요한 에러도 있었지만, 애플리케이션과 무관한 에러도 포함되어 있었기 때문에 이를 구별하는데 어려움이 있었습니다.
개선 방안 - 수집 대상 정의
초기에는 발생하는 비즈니스 에러를 폭넓게 수집했습니다. 하지만 실제 운영 과정에서는 정상적인 업무 제약이나 사용자 입력 실수까지 모두 수집되면서 노이즈가 많아졌고, 중요한 장애를 구분하기 어려웠습니다.
그래서 모든 에러를 동일하게 취급하지 않고, 현장 업무에 직접적인 영향을 주는 API를 중심으로 수집 정책을 재정의했습니다.
내부적으로는 CSP(Critical Service Policy)를 기준으로 장애 발생 시 업무가 중단되는 API들을 분류했고, 해당 API에서 발생하는 에러를 우선적으로 관측하도록 했습니다.
| 업무 영역 | 주요 업무 | CSP 선정 이유 |
|---|---|---|
| 입고(Inbound) | 입고 신청, 입고 완료, 로케이션 지정, 작업 생성 | 실패 시 상품 입고 작업 중단 |
| 출고(Outbound) | 피킹 생성, 송장 발행, 상차 완료, 출고 완료 | 실패 시 출고 프로세스 중단 |
| 패킹(Packing) | 패킹 완료, 송장 발행, 패킹 슬립 스캔 | 실패 시 패킹 작업 중단 |
| 반품(Returns) | 반품 조회, 반품 상세 조회 | 실패 시 반품 처리 불가 |
어떻게 달라졌을까? Session Replay 활용한 사례
Session Replay와 Breadcrumb로 발견한 Race Condition
Session Replay를 활용하면서 재현이 어려웠던 문제를 해결한 경험도 있었습니다.
어느 날 B2C 패킹 작업 중 검수를 완료했는데, 사용자가 원래 이동해야 하는 화면이 아니라 포장재 입력 페이지로 이동한 뒤 "시스템 오류가 발생하였습니다" 메시지를 보게 되는 문제가 발생했습니다.
더 이상했던 점은 해당 화면이 실제로는 사용되지 않는 화면이었다는 것입니다. 코드상으로는 존재했지만 현재 비즈니스 로직에서는 진입할 수 없는 조건이라고 생각하고 있었기 때문에 처음에는 원인을 전혀 짐작할 수 없었습니다.

Session Replay를 확인해보니 사용자가 어떤 화면을 거쳐 이동했는지 확인할 수 있었고, Breadcrumb를 통해 API 요청과 사용자 이벤트 순서를 추적할 수 있었습니다.
분석 결과 검수 완료 이벤트가 패킹슬립 조회 API 응답보다 약 8ms 먼저 발생하고 있었습니다.

07:34:15.088 검수 완료 이벤트 발생
07:34:15.096 패킹슬립 조회 API 응답 완료정상적인 경우에는 패킹슬립 조회 API 응답을 받은 뒤 해당 데이터를 기반으로 다음 화면을 결정합니다.
패킹슬립 조회 API 요청
↓
응답 수신
↓
다음 화면 결정
↓
사용자 검수 완료하지만 문제가 발생한 경우에는 네트워크 지연으로 인해 순서가 달라졌습니다.
패킹슬립 조회 API 요청
↓
(응답 대기 중)
↓
사용자 검수 완료
↓
다음 화면 결정
↓
API 응답 도착이 시점에는 API 응답이 아직 도착하지 않았기 때문에 실제 데이터가 아닌 초기값(null)을 기준으로 화면 분기가 수행되고 있었고, 그 결과 원래는 진입할 수 없다고 생각했던 화면으로 이동하게 된 것이었습니다.
사무실 환경에서는 재현이 전혀 되지 않았지만, 네트워크 환경이 상대적으로 열악한 물류센터에서는 충분히 발생할 수 있는 문제였습니다.
결국 원인은 필요한 데이터가 준비되기 전에 화면 전환 로직이 실행되면서 발생한 Race Condition이었습니다.
만약 기존 방식대로 원인을 찾으려고 했다면 네트워크 환경을 맞춰가며 재현에 많은 시간을 사용했을 것입니다. 하지만 Session Replay와 Breadcrumb를 통해 사용자의 실제 행동 흐름과 API 응답 순서를 확인할 수 있었고, 재현 없이도 원인을 빠르게 특정할 수 있었습니다.
그 이외에 어떤 것을 발견했을까?
모니터링을 도입하면서 장애 대응 외에도 예상하지 못했던 여러 인사이트를 얻을 수 있었습니다.
먼저 Web Vital 데이터를 수집하면서 물류센터 환경이 생각보다 더 열악하다는 사실을 확인할 수 있었습니다. 사무실 환경에서는 쉽게 재현되지 않던 문제들이 실제 현장에서는 네트워크 지연이나 낮은 사양의 PC 환경으로 인해 충분히 발생할 수 있었습니다.
또한 처음에는 창고 계정에서 발생하는 장애를 중심으로 관측을 시작했지만, 실제 데이터를 살펴보니 셀러 계정에서도 다양한 오류가 발생하고 있었습니다. 장애가 발생하는 지점을 정량적으로 확인하면서 기존에 가지고 있던 가정과 실제 사용자 경험 사이에 차이가 있다는 점도 알 수 있었습니다.
마무리
약 한 달 정도 모니터링을 운영해보면서 느낀 점은 에러를 많이 수집하는 것보다 어떤 에러를 관측할 것인지 정의하는 것이 더 중요하다는 점이었습니다.
초기에는 가능한 많은 정보를 수집하는 것이 중요하다고 생각했지만, 실제로는 노이즈가 많아질수록 중요한 장애를 놓칠 가능성도 함께 커졌습니다. 그래서 CSP를 기준으로 업무에 직접적인 영향을 주는 장애를 우선적으로 관측하도록 수집 정책을 정리했습니다.
또한 Session Replay와 Breadcrumb를 활용하면서 단순히 장애를 탐지하는 것을 넘어 사용자의 실제 행동 흐름과 서비스 사용 경험을 이해할 수 있었습니다. 덕분에 재현이 어려운 문제도 더 빠르게 분석할 수 있었고, 사용자 경험 개선에도 활용할 수 있었습니다.
아직 개선해야 할 부분은 많지만 앞으로도 수집 정책을 지속적으로 다듬으면서 노이즈는 줄이고, 실제 대응이 필요한 장애에 집중할 수 있는 방향으로 발전시켜 나갈 예정입니다.
결국 이번 경험을 통해 모니터링의 목적은 에러를 많이 수집하는 것이 아니라 서비스의 상태를 더 잘 관측하고 이해하는 것이라는 점을 배울 수 있었습니다.