
현재 운영하고 있는 사물함 대여 반납을 관리하는 웹서비스인 Cabi에서는 버그 혹은 예상치 못한 에러 발생 시 사용자들의 신고로 이를 후속 처리하고 있다.
production으로 deploy하기 전 dev 서버를 한번 거치지만 모든 사용자 디바이스와 브라우저를 고려한 런타임 환경에 대응하기 위한 완벽한 예외처리는 사실상 어렵다. 하물며 프론트엔드 코드는 현재 테스트 코드 또한 부재한 상태이다.
때문에 기존의 주먹구구식 버그 리포팅 방식으로부터 탈피해 에러 추적을 위한 로깅을 도입하자는 게 센트리 도입의 첫 아이디어였다.
Sentry는 실시간으로 어플리케이션의 런타임 환경 내 발생하는 에러를 추적하고 또 이를 로깅 및 분석해 주는 모니터링 플랫폼이다.

현재 Cabi가 사용하는 프론트엔드 라이브러리는 React이므로, React 기준으로 센트리 SDK를 설치하였다. 위 사진을 보면, Sentry의 SDK는 리액트 외에도 정말 다양한 언어와 라이브러리, 프레임워크와 호환성을 띄는 것을 볼 수 있다.

가이드라인대로 따라오면 프로젝트에 SDK를 설치하는 방법을 아주 친절하게 알려준다. 리액트의 경우 사용하는 패키지 매니저에 맞는 명령어로 설치 후, 어플리케이션의 엔트리포인트가 되는 파일에서 Sentry를 initialize해주면 된다.
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
release: "5.0.0", // 현재 Cabi 버전
enabled: import.meta.env.VITE_IS_PROD, // production이면 true
... // 나머지 코드
});
init 메서드의 인자로 주어지는 객체 내 프로퍼티들이 각자 뭘 의미하는지는 Sentry 홈페이지에서 확인하기를 바란다. 주의해야 할 부분은, 위 dsn은 프로젝트를 고유하게 식별하기 위한 키와 같은 역할이기에 리터럴로 입력하기보다는 환경 변수로 관리하는 편이 좋을 것이다. 또한 enabled 프로퍼티를 활용해 어떤 개발 환경에서 에러를 추적할지 정할 수 있다.
Sentry에서는 에러와 메시지를 로깅하기 위해 주로 captureException과 captureMessage 함수를 사용한다. 이 두 함수는 각각 다른 유형의 정보를 캡처하고 리포트하는 데 사용되며, 사용 사례에 따라 선택할 수 있다.
try {
...
} catch (error) {
Sentry.captureException(error);
}
일반적으로 예외가 발생한 경우에 사용된다. captureException을 사용하면 인자로 error 객체를 전달해주기 때문에 Sentry가 전체 스택 트레이스를 캡처하고 해당 오류를 보다 자세히 분석할 수 있다.
try {
...
} catch (error) {
Sentry.captureMessage("전달하고자 하는 메시지");
}
captureMessage는 예외나 오류가 아닌, 명시적으로 추적하고자 하는 정보를 Sentry로 보내고 싶을 때 사용한다. 예를 들어, 특정 이벤트의 발생을 로깅하거나 디버깅 정보를 수동으로 보고하고자 할 때 유용하다. 이 방법은 예외가 발생하지 않기 때문에 스택 트레이스는 제공하지 않는다.
<Sentry.ErrorBoundary>
<RecoilRoot>
<App />
</RecoilRoot>
</Sentry.ErrorBoundary>
Sentry가 제공하는 ErrorBoundary 컴포넌트로 최상위 루트 컴포넌트를 감싸주면 자식 컴포넌트 트리에서 발생하는 모든 렌더링 오류, 생명주기 메소드 내 오류, 그리고 그 아래에 있는 컴포넌트 생성자 내의 오류를 포착할 수 있다.
그러나 Axios와 같은 HTTP 클라이언트를 통해 발생하는 비동기 오류를 직접적으로 포착하지 못한다. Axios 에러는 네트워크 요청 과정에서 발생하는 것으로, 이는 ErrorBoundary가 감지할 수 있는 컴포넌트 라이프사이클 또는 렌더링 과정의 오류와는 다르기 때문이다.
때문에 Sentry가 Axios 에러를 포착하기 위해서는 개발자가 직접 try catch와 같은 에러 처리 구문에서 captureException과 같은 함수를 통해 에러 로깅을 해줘야 한다.
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
logAxiosError(error);
... // 나머지 코드
}
);
Cabi에서는 Axios interceptors를 사용해 axios 객체에 대한 에러 핸들링을 전역적으로 관리한다. 때문에 axios 에러가 발생했을 시 반환되는 에러 객체를 직접 만든 logAxiosError 함수의 인자로 전달해주면 모든 api에 대한 에러 핸들링을 할 수 있게 된다.
export function logAxiosError(error: AxiosError) {
const errorMessage =
"[Axios] " + getAxiosErrorMsg(error.response?.config.url);
Sentry.withScope((scope: Sentry.Scope) => {
scope.setTags({
api: error.response?.config.url,
"api.request.method": error.config?.method?.toUpperCase(),
"api.response.status": (error.response?.status || "").toString(),
});
scope.setLevel(Severity.Error);
error.message = errorMessage;
Sentry.captureException(error, { fingerprint: [errorMessage] });
});
}
logAxiosError 함수는 대략 이렇게 구성된다.
먼저 어떤 유형의 api 요청이 실패했는지 식별하기 위해 getAxiosErrorMsg 함수를 통해 uri를 파싱해 에러 메시지를 전달받는다.
(예: /v4/lent로 시작하면 "사물함 대여 관련", /v4/users로 시작하면 "유저 관련")
Sentry.withScope 메소드를 활용해 오류 컨텍스트에 대한 임시 범위를 지정해준다. 쉽게 말하자면, 현재 로깅에 대한 부가 정보를 추가할 수 있는 기능이다.
setTags 메소드를 통해 원하는 태그를 추가할 수 있고, setLevel 메소드를 통해 현재 발생한 오류의 심각도를 커스텀할 수 있다.
마지막으로 captureException 메소드를 통해 현재 발생한 에러를 센트리로 전달한다. 두번째 인자의 프로퍼티인 fingerprint는 오류를 사용자 정의로 그룹화하는 데 사용하는 식별자이다.
const axiosLentIdURL = "/v4/lent/에러발생시키기";
자 그럼, 사물함을 대여 요청 api uri를 일부러 이상하게 바꾸어 에러를 발생시켜보자.

에러가 발생했을 때, Sentry에 새로운 이슈가 잘 등록이 된 것을 확인할 수 있다.

이슈를 클릭해서 들어가보면, 등록했던 태그들 또한 잘 들어가있는 것을 발견할 수 있다.
새로운 이슈가 발생했을 시, 기존에 사용하고 있는 협업툴과 연동해 알림을 전달할 수 있다. Cabi에서는 discord를 주로 사용하고 있으니 discord에 연동을 시켜보았다.
참고로 연동 방법은 Sentry 홈페이지에 잘 설명이 되어 있으니 직접 수행하기 어렵지 않을 것이다.

연동 후에는 Alert Rules를 직접 설정해 줘야 한다. Alert Rules 섹션에서는 WHEN, IF, THEN을 통해 언제, 어떤 알림을, 만약에 ~ 조건이 충족된다면, 어떤 액션을 취할지 커스텀한 알림을 설정할 수 있다.
필자의 경우 일단 새로운 이슈가 발생할 때마다 연동한 discord 서버에 이슈를 로깅하도록 설정하였다.
연동과 알림 설정을 끝마치고 에러를 발생시켜보면, 성공적으로 discord에 이슈에 대한 알림을 전송하는 것을 확인할 수 있다.
