Sentry를 도입하게 된 이유는 마치 "불이 난 집을 찾는 소방관의 이야기"처럼 시작됐다.
한창 프로젝트를 진행하며 새로운 기능을 개발하는 데에 집중했고, 갑자기 예상치 못한 에러들이 쏟아지기 시작했다. 문제는 어디서 에러가 발생했는지 알 수 없었다. 마치 넓은 숲에서 어디선가 연기는 보이는데 불이 난 정확한 위치를 모르는 것처럼 말이다. 멘토님 추천으로 Sentry라는 도구를 발견하게 되었고, Sentry는 실시간으로 에러를 감지하고, 에러가 발생한 지점과 그 순간의 상황을 바로 기록해주었다.
프로젝트 중에는 Sentry를 제대로 활용은 해보지 못했지만, 인상깊었던 도구이니 만큼 다시 Sentry에 대해 알아가보고자한다.
실시간 로그 취합 및 분석 도구이자 모니터링 플랫폼
(1) captureException
: 에러 객체 & 문자열 전송 ⇒ Sentry 서버
import * as Sentry from '@sentry/react';
try {
fetchAccountInfoApi();
} catch (error) {
Sentry.captureException(error);
}
(2) captureMessage
: 문자열만 전송 ⇒ Sentry 서버
import * as Sentry from '@sentry/react';
Sentry.captureNessage('API 서버 에러가 발생하였습니다! ')
Response 인터셉터: API 호출이 실패할 때 Sentry에 에러 정보를 전송하도록 설정
[401 Unauthorized 처리]
세션 만료로 인한 401 Unauthorized 에러 발생 시,
Sentry.captureException(error)를 통해 새로고침 토큰 요청 실패 시 에러를 Sentry에 기록
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const { isLoggedIn, setIsLoggedIn } = useLoginStore.getState();
const { refreshToken } = getTokenFromStorage();
// 401 Unauthorized 처리
if (isLoggedIn && err.response?.status === 401) {
...
return axiosInstance(originRequest);
} catch (error) {
// Sentry로 에러 전송
Sentry.captureException(error);
...
return Promise.reject(error);
}
}
return Promise.reject(err);
}
)
(1) scope
sentry는 기본적으로 스코프 단위로 데이터를 관리한다.
스코프 : 각각의 이벤트(에러나 이슈)가 발생할 때 함께 전송되는 추가적인 정보를 관리하는 컨텍스트
withScope
와 configureScope
라는 두 가지 함수를 통해 더 세밀하게 제어할 수 있다.configureScope
: 전역 스코프를 수정할 때 사용. 즉, 이후의 모든 이벤트에 영향을 미침.withScope
: 특정 이벤트에만 영향을 미치는 스코프를 임시로 생성하여 관리. 일시적인 스코프 조정이 필요할 때 유용.configureScope로는 공통 정보를, withScope로는 각 에러 상황 시 추가 정보를 전송한다.
[configureScope]
import * as Sentry from '@sentry/react';
Sentry.configureScope((scope) => {
scope.setUser({
accountId: 2022,
email: 'chloe.ykim@nana.na',
});
scope.setTag('webviewType', 'WEB');
});
[withScope]
import * as Sentry from '@sentry/react';
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setLevel(Sentry.Severity.Error);
Sentry.captureException(new Error('API Internal Server Error'));
});
일반적인 에러 처리:
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const { isLoggedIn, setIsLoggedIn } = useLoginStore.getState();
const { refreshToken } = getTokenFromStorage();
// 401 Unauthorized 처리
if (isLoggedIn && err.response?.status === 401) {
...
return axiosInstance(originRequest);
} catch (error) {
// Sentry로 에러 전송
Sentry.captureException(error);
...
return Promise.reject(error);
}
}
// Sentry로 일반적인 에러 전송
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('url', err.config.url);
scope.setLevel('error');
Sentry.captureException(err);
});
return Promise.reject(err);
}
)
(2) context
이벤트에 임의의 데이터를 연결할 수 있는 context를 이용해 추가 정보를 전송,
검색은 할 수 없고 해당 이벤트가 발생한 이벤트 로그에서 확인 가능
const { method, url, params, data: requestData, headers } = error.config;
const { data: responseData, status } = error.response;
Sentry.setContext('API Request Detail', {
method,
url,
params,
data: requestData,
headers
});
Sentry.setContext('API Response Detail', {
status,
data: responseData
});
[API Request details]
(3) Customized Tags
에러 추적을 신속하게 하기 위해서는 검색이 중요하다.
태그는 인덱싱이 가능하기 때문에 이벤트에 빠른 접근이 가능하다
import * as Sentry from '@sentry/react';
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('api', 'general');
scope.setLevel(Sentry.Severity.Warning);
Sentry.captureException(new Error('API Internal Server Error'));
});
(4) Level
중요도를 식별할 수 있다.
import * as Sentry from '@sentry/react';
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('api', 'general');
scope.setLevel(Sentry.Severity.Error); // Error level #8
Sentry.captureException(new Error('API Internal Server Error'));
});
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('api', 'timeout');
scope.setLevel(Sentry.Severity.Warning); // Warning level 18
Sentry.captureException(new ApiError('API Timeout Error'));
});
(5) lssue Grouping
각각의 이벤트들은 내재화된 그룹화 알고리즘에 의해 생성된 fingerprint를 가지고 있다.
fingerprint는 자동으로 하나의 이슈로 그룹화 되는데, 이슈가 예상한 것과 다르게 보여서 재설정이 필요한 경우가 있다.
import * as Sentry from "@sentry/react";
// axios error
const { method, url } = error.config;
const { status } = error.response;
// Using withScope to set fingerprint
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('api', 'general');
// Corrected the fingerprint array
scope.setFingerprint([method, status, url]);
Sentry.captureException(new Error('API Internal Server Error'));
});
Fingerprint 설정:
scope.setFingerprint([method, status.toString(), url]);
에서 method(GET, POST 등), status(응답 상태 코드), url(API 요청 경로)을 기반으로 fingerprint를 설정하여 각각의 API 요청과 응답 상태에 따라 에러를 구분
// Sentry에 에러와 함께 fingerprint 설정
Sentry.withScope((scope) => {
scope.setTag('type', 'api');
scope.setTag('url', err.config.url);
scope.setLevel('error');
// Fingerprint 설정 (HTTP 메서드, 상태 코드, 요청 URL)
const { method, url } = err.config;
const { status } = err.response;
scope.setFingerprint([method, status.toString(), url]);
Sentry.captureException(err);
});
에러 쌓기 작업 후 에러 상황을 빠르게 감지하기 위해 severity 기준 설정과 그에 따른 모니터링 필요
(1) 알람 조건 설정
[참고자료]
https://www.youtube.com/watch?v=012IPbMX_y4&t=385s
https://tech.kakaopay.com/post/frontend-sentry-monitoring/