많은 개발자들에게 "장애"라는 단어는 언제나 익숙하지 않은 단어이자 상황일 것입니다. 서비스 제공자의 입장에서 대형 서비스의 장애는 매출 하락뿐만 아니라, 사용자와 쌓아왔던 신뢰를 잃을 위험이 있습니다. 따라서 장애 탐지와 대응은 신속하고 정확해야 합니다. 장애는 원인을 파악하기 전까지 인프라 문제인지, 데이터 문제인지, 혹은 백엔드나 프론트엔드의 문제인지 알기 어렵습니다. 이러한 이유로 장애가 발생하면 관련된 모든 개발자들이 원인을 찾기 위해 고군분투하게 됩니다. 예상치 못한 원인으로 인해 장애가 발생할 수 있으며, 이는 철저한 QA를 거친 시스템에서도 예외는 아닙니다.
프론트엔드에서의 오류는 크게 데이터 영역과 화면 영역에서의 오류, 그리고 예상할 수 없는 네트워크 이슈, 특정 브라우저 버전이나 단말기 OS 업데이트 같은 외부 요인에 의한 오류로 나눌 수 있습니다. 데이터와 화면에서 발생하는 오류는 충분히 방어할 수 있지만, 외부 요인에 의한 오류나 런타임 오류는 예측하기 어렵습니다. 이러한 오류는 QA나 Staging 환경에서 잘 재현되지 않기 때문에 언제든지 발생할 수 있습니다.
클라이언트에서 발생하는 오류를 트래킹할 수 있다면, 오류를 신속하게 탐지하고 정확하게 원인을 파악하여 빠르게 대응할 수 있습니다. 이에 카카오페이 프론트엔드 개발자들은 Sentry라는 프론트엔드 모니터링 툴을 통해 이러한 오류에 대응하고 있습니다.
Sentry는 실시간 로그 취합 및 분석 도구이자 모니터링 플랫폼으로, 다양한 정보를 제공하고 이벤트별로 발생 빈도를 알 수 있으며, 설정에 따라 알림을 받을 수 있습니다. 또한 로그를 시각화 도구로 쉽게 분석할 수 있도록 도와주며 다양한 플랫폼을 지원합니다. 이 문서에서는 프로젝트에 Sentry를 연동하는 과정과 Sentry의 강력한 기능들을 설명하고자 합니다.
sentry.io 에 접속 후 energyx 계정으로 로그인을 한 후 [사진 1] 와 같이 organization 을 만들고 선택할 수 있습니다.

[사진 1]

[사진 2]
모든 organization 은 [사진 2] 와 같은 메뉴를 가지고 있습니다.
애플리케이션에서 발생한 에러와 예외를 추적하고 나열합니다. 사용자는 이 섹션에서 에러의 상세 정보를 확인하고, 문제 해결을 위해 필요한 조치를 취할 수 있습니다.
Sentry에서 관리하는 개별 프로젝트들을 나타냅니다. 사용자는 여기서 프로젝트 설정을 관리하고 각 프로젝트별로 발생하는 이슈를 모니터링할 수 있습니다.
애플리케이션의 성능 관련 데이터를 모니터링하고 분석합니다. 응답 시간, 트랜잭션, 지연 시간 등의 성능 지표를 제공하여 애플리케이션의 성능 병목 현상을 파악하고 최적화할 수 있도록 돕습니다.
애플리케이션의 성능 프로파일을 제공하여, 성능 저하의 원인을 파악하고 개선할 수 있게 돕습니다.
애플리케이션의 다양한 메트릭스(지표)를 수집하고 분석합니다. 베타 버전이라고 표시되어 있어 아직 개발 중이거나 테스트 단계일 수 있습니다.
사용자의 세션을 재생하여 문제가 발생한 상황을 시각적으로 이해할 수 있도록 합니다.
사용자로부터 직접 피드백을 수집합니다. 이를 통해 사용자의 문제를 더 잘 이해하고, 개선점을 찾을 수 있습니다.
배치 작업이나 정기적인 작업을 관리합니다.
애플리케이션에서 문제가 발생했을 때 사용자에게 경고를 보내도록 설정할 수 있는 기능입니다.
센트리가 가지고 있는 기능들이 많지만 현재 단계에서 제가 사용한 기능들은 위에서 노랗게 색칠한 메뉴들입니다.
디자인 후즈 클라이언트를 사용하는 사용자들을 모니터링하는 것을 목표로 시작하였습니다. whos-fe-client 프로젝트와 sentry 를 연동하는 방법은 다음과 같습니다.

[사진 3]
위 사진에서 designwhos-fe 는 제가 이미 최종적으로 완성한 프로젝트입니다. 다음과 같이 프로젝트를 생성해서 연결해주기만 하면 sentry 연동은 끝입니다. create Project 를 클릭합니다.

[사진 4]

[사진 5]
프로젝트로 이동하여 npx @sentry/wizard@latest -i nextjs 명령 실행
Running Sentry Wizard...
version: 3.21.0 | sentry-cli version: 1.77.3
Sentry Wizard will help you to configure your project
Thank you for using Sentry :)
Skipping connection to Sentry due files already patched
┌ Sentry Next.js Wizard
│
◇ ────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ The Sentry Next.js Wizard will help you set up Sentry for your application. │
│ Thank you for using Sentry :) │
│ │
│ Version: 3.21.0 │
│ │
│ This wizard sends telemetry data and crash reports to Sentry. This helps us improve the Wizard. │
│ You can turn this off at any time by running sentry-wizard --disable-telemetry. │
│ │
├───────────────────────────────────────────────────────────────────────────────────────────────────╯
│
◇ Are you using Sentry SaaS or self-hosted Sentry?
│ Sentry SaaS (sentry.io)
│
◇ Do you already have a Sentry account?
│ Yes
│
● If the browser window didn't open automatically, please open the following link to log into Sentry:
│
│ https://sentry.io/account/settings/wizard/bplimyuuks1m0lpb33gmhkm7p5f9x2vtmxe30tu49qifi1nqjass7mhrevhdhmm4/
│
◇ Login complete.
│
◇ Select your Sentry project.
│ designwhos/javascript-nextjs
│
◇ Installed @sentry/nextjs@^7.105.0 with NPM.
│
◆ Created fresh sentry.server.config.ts.
│
◆ Created fresh sentry.client.config.ts.
│
◆ Created fresh sentry.edge.config.ts.
│
◆ Added Sentry configuration to next.config.js. (you probably want to clean this up a bit!)
│
● It seems like you already have a custom error page.
│
│ Please add the following code to your custom error page
│ at src/pages/_error.tsx:
import * as Sentry from '@sentry/nextjs';
import type { NextPageContext } from "next";
// Replace "YourCustomErrorComponent" with your custom error component!
YourCustomErrorComponent.getInitialProps = async (contextData: NextPageContext) => {
await Sentry.captureUnderscoreErrorException(contextData);
};
│
◇ Did add the code to your src/pages/_error.tsx file as described above?
│ Yes
│
◇ Do you want to create an example page ("/sentry-example-page") to test your Sentry setup?
│ Yes
│
◆ Created src/pages/sentry-example-page.js.
│
◆ Created src/pages/api/sentry-example-api.js.
│
◆ Added auth token to .sentryclirc for you to test uploading source maps locally.
│
◆ Created .sentryclirc.
│
◆ Added .sentryclirc to .gitignore.
│
◇ Are you using a CI/CD tool to build and deploy your application?
│ Yes
│
◇ Add the Sentry authentication token as an environment variable to your CI setup:
SENTRY_AUTH_TOKEN=a2bf4d3a4cd042f08a503b888feb69d4aa6976a7e72d43c5954ffd581e8417db
│
▲ DO NOT commit this auth token to your repository!
│
◇ Did you configure CI as shown above?
│ I'll do it later...
│
● Don't forget! :)
│
└
Successfully installed the Sentry Next.js SDK!
You can validate your setup by restarting your dev environment (next dev) and visiting "/sentry-example-page"
If you encounter any issues, let us know here: https://github.com/getsentry/sentry-javascript/issues
🎉 Successfully set up Sentry for your project 🎉
[설치마법사 플로우]
설치를 완료하고 나면 다음과 같은 파일들이 만들어집니다. 대략적인 설명은 참고용으로 적어놨습니다.

[사진 6]

[사진 7]
위와 같이 프로젝트가 정상적으로 생성 됐습니다.

[사진 8]
프로젝트의 내부로 들어가면 아무것도 나오지 않습니다. 프로젝트 연동 후 런타임 에러가 발생한 적이 없기 때문입니다.
이제 sentry-example-page.jsx 를 이용해서 에러를 직접 발생시켜 보겠습니다.

[사진 9]
위 의 버튼은 다음과 같은 이벤트 함수를 호출합니다.
onClick={() => {
Sentry.startSpan({
name: 'Example Frontend Span',
op: 'test'
}, async () => {
const res = await fetch("/api/sentry-example-api");
if (!res.ok) {
throw new Error("Sentry Example Frontend Error");
}
});
}}
에러를 발생 시킨 후 sentry 가 제대로 에러를 추적하는지 확인해봤습니다.

[사진 10]

[사진 11]
위 사진과 같이 에러로그가 잘 쌓인게 보입니다. 각 에러들은 API 를 호출할 때 발생한 에러와 throw new Error("Sentry Example Frontend Error") 를 통해서 프론트에서 런타임에서 발생시킨 에러를 모두 추적하는데 성공했습니다. 이제 발생한 에러의 상세를 보도록 하겠습니다.




[사진 12]
에러의 상세페이지에서 제공하는 사용자환경에 대한 정보입니다.
sentry가 제공하는 기능이 많지만 필요에 따라서 기능을 고도화 할 계획이고 현재 제가 해결해야 할 과제들은 다음과 같습니다.
오류가 로그화 되는것은 위의 과정을 통해서 확인했습니다. 하지만 치명적인 에러가 발생한다고 해도 대응하는데 오랜 시간이 걸린다면 서비스를 하는데 치명적인 요인이 될 수 있습니다. 따라서 sentry 에서는 Alerts 라는 메뉴를 통해서 실시간으로 모니터링을 할 수 있게 해줍니다.

[사진 13]
Alerts 메뉴에서 Create Alert 버튼을 클릭합니다.

[사진 14]
다음과 같이 생성할 수 있는 Alert 의 종류는 많지만 현재 저희 plan 에서 issues 만 선택할 수 있으니 다음으로 넘어갑니다.

[사진 15]
Alert 의 설정은 다음과 같습니다.
로 나눌 수 있습니다.
그리고 저는 고도화를 하기 전까지는 모든 에러에 대해서 추적을 하는것이 좋을 것이라고 판단하였고 [사진 16] 와 같은 설정을 하게 되었습니다.

[사진 16]
에러는 결론적으로 발생하는 모든 에러에 대해서 5분 간격으로 디자인후즈의 특정 채널에 알림을 보내는 설정을 하였고 다음과 같이 성공적으로 에러를 팀즈알림으로 받을 수 있었습니다.

[사진 17]
현재는 모든 status api 에러에 대해서 Sentry 가 에러를 캐치하도록 설정하였습니다. 후에 500 번 이상의 에러만 캐치하도록 설정하기 위해서 status ≥ 500 일 때 조건문만 추가해주면 됩니다.
$axios.interceptors.response.use(
(res) => res,
async (err) => {
const { method, url, params, data, headers: _headers } = err.config;
const { data: responseData, status } = err.response;
const { responseURL } = err.request;
const headers: { [key: string]: any } = {};
Object.entries(_headers).forEach(([key, value]) => (headers[key] = value));
Sentry.setContext("API Request Detail", {
method,
url,
params,
data,
headers,
});
Sentry.setContext("API Response Detail", {
status,
responseData,
});
Sentry.setTag("type", "axiosError");
Sentry.captureMessage(`${responseURL} (${status})`, { level: "error" });
if (err.response.status === 401 || err.response.status === 403) {
window.location.href = `${process.env.NEXT_PUBLIC_LOGOUT_URL}`;
return;
}
if (err.response.status === 404) {
if (typeof window === "undefined") throw err;
window.location.replace(`${process.env.NEXT_PUBLIC_MY_URL}/404`);
return;
}
throw err;
}
)
[axios.ts]
위의 코드를 보면 setContext 와 setTag 가 있습니다. 아래 사진과 같이 setContext 로 설정한 영역이 issue 의 상세정보에 추가됩니다.

[사진 18]
또 setTag 를 이용해서 issue 를 태깅할 수 있는데 태깅 된 에러들을 그룹화 하여 조회 할 수 있는 기능이 있습니다.
Sentry.init({
dsn: "https://...",
environment: "production",
enabled: process.env.NODE_ENV === "production",
beforeSend(event, hint) {
const axiosArror = hint.originalException as AxiosError;
if (axiosArror.isAxiosError) return null;
return event;
},
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
Sentry.replayIntegration({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],
})
[sentry.client.config.ts]
import { useMemberInfo } from "@/domain/member/api/member.query";
import * as Sentry from "@sentry/nextjs";
export const useUserInfo = () => {
const { data: memberInfoData } = useMemberInfo(isLogin);
const sentryUserInfo = memberInfoData ? memberInfoData : null;
Sentry.getCurrentScope().setUser(sentryUserInfo);
return {
...
}
};
[useUserInfo.hook.ts]

[사진 19]

프론트엔드에서의 오류는 크게 데이터 영역, 화면 영역 두 가지 영역에서의 오류, 그리고 예상할 수 없는 네트워크 이슈나 특정 브라우저 버전, 단말기 OS 업데이트 같은 외부 요인에 의한 오류나 예상치 못한 런타임 오류로 나눌 수 있습니다. sentry 가 예측 불가능한 모든 에러를 다 캐치해서 로그화 해줄 수 있을지는 사실 위 과정을 겪는다고 해도 판단하기가 어렵습니다. 하지만 위에서 문제로 정의한 문제들을 모두 해결할 수 있었고 런타임에서 발생하는 모든 자바스크립트 오류 (RangeError, ReferenceError, SyntaxError, TypeError 등) 를 경험상 성공적으로 모니터링 할 수 있었습니다.