우당탕탕 센트리 도입기

임홍원·2024년 12월 11일

최근 입사를 하게 된 후 나에게 하나의 업무가 주어졌다.
바로 에러 모니터링을 위해 Sentry를 도입하는것이였다!

여러 자료를 찾아보고 어떻게 센트리를 도입했는지 적어보겠다.

문제상황

현재 웹에서 발생하는 모든에러가 Setnry에 잡히고 있었다.
특히 개발환경 및 로컬환경에서의 에러도 잡히고 있었고 디버깅할 필요도 없는 에러 이벤트들도 Sentry에 잡히고 있는 중이였다

또한 에러 이벤트를 계속 Sentry에 적재중이라 비용이 증가하는 중이였다.

따라서 여러가지를 생각해보았다!

생각해본 고민점들

  1. 프로덕션과 개발환경에서만 에러를 수집
  2. 예상치 못한 외부 요인에 의해 에러가 발생하거나, 런타임에서 에러가 발생할때만 Sentry에 이벤트 전달방법 디버깅에 필요한 userId와 같은 추가적인 데이터를 함께전달 (필터링)
  3. 에러 수집 비율 감소시키기 (sampleRate)
  4. 500 에러 발생시에만 Sentry에 전달하도록 해야하나?
  5. ErrorBounday를 도입하여 Sentry에 에러 보내기

여러가지를 생각해보았고 제일 적합한 방법으로는 프로덕션 환경과 개발환경에서만 에러를 수집하고 Status code에 따라 수집하기로 하였다!

이제 코드로 들어가보자!

sentry.client.config.ts

import * as Sentry from "@sentry/nextjs";
import Constant from "./src/global/constants/Constant";

Sentry.init({
  dsn: Constant.sentryDsn,

  // 성능 모니터링 데이터의 샘플링 비율을 정의합니다.
  tracesSampleRate: 1,

  // 설정 중에 도움이 되는 정보를 콘솔에 출력 (true로 설정시)
  debug: false,

  // 에러 발생 시 세션 리플레이를 캡처할 비율을 설정합니다.
  replaysOnErrorSampleRate: 1.0,

  // 에러와 관계없이 세션 리플레이를 캡처할 비율을 설정합니다.
  replaysSessionSampleRate: 0.1,

  // Sentry와 함께 사용할 추가 기능이나 통합을 설정
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
  tracePropagationTargets: [
    process.env.NEXT_PUBLIC_DOMAIN as string,
    process.env.NEXT_PUBLIC_API_BASE_URL as string,
    process.env.NEXT_PUBLIC_FILE_API_URL as string,
  ],
  ignoreErrors: [/AxiosError/i],
  beforeSend(event, _) {
    if (window.location.hostname === "localhost") {
      return null;
    }
    return event;
  },
});

혹시 모를 브라우저 환경에서의 에러도 잡기위해 browserTracingIntegration 옵션도 사용해주었다.

그리고 어느 도메인과 어느 API에서 에러가 발생하는지 추적하기위해
tracePropagationTargets 옵션을 넣어주었다.

또한 beforeSend 옵션을 통해 로컬환경이면 event를 보내지 않도록 해주었다!

Class

센트리를 공용으로 사용하기 위해 클래스로 정의해주었다.

import * as Sentry from "@sentry/nextjs";

class SentryHelper {
  static handleNetworkError(error: any) {
    const requestUrl = error.config?.url || "URL 정보 없음";
    Sentry.withScope((scope) => {
      scope.setLevel("error");
      scope.setTag("error type", "Network Error");
      scope.setContext("Axios Request", {
        url: requestUrl,
        method: error.config?.method,
        headers: error.config?.headers,
        data: error.config?.data,
      });
      Sentry.captureMessage(
        `[Network Error] ${requestUrl} \n${error.message ?? `네트워크 오류`}`
      );
    });
  }

  static handleApiError(error: any) {
    const isServerError = error.response.status >= 500;
    const errorType = isServerError ? "Server Error" : "Api Error";
    Sentry.withScope((scope) => {
      scope.setLevel("error");
      scope.setTag("error type", errorType);
      Sentry.captureMessage(
        `[${errorType}] ${error.config.url} \n${error.message}`
      );
    });
  }
}

export default SentryHelper;

Scope

에러를 Scope 단위로 구분지어서 사용 할 수 있다.
Scope에 User의 정보를 넣어서 어떤 유저가 어떤 에러를 겪는지 확인할 수 있음 (setUser) 즉 데이터 재구성이 가능함!

태그와 레벨 정보를 같이 넣어 조금 더 파악하기 쉽게 할 수 있다.

request

apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    // console.log(error);
    if (!error.response) {
      SentryHelper.handleNetworkError(error);
    } else if (
      error.response.status >= 400 &&
      ![401, 409].includes(error.response.status)
    ) {
      SentryHelper.handleApiError(error);
    }
    return Promise.reject(error);
  }
);

export default apiClient;

현재 회사에서는 axios 를 사용중이다.
그래서 axios interceptor 에 Sentry 코드를 넣어주었다.

400번대 에러의 경우 401과 409를 제외하고 모두 수집하도록 정해주었다.

결과

센트리를 도입한것이 12월 초 이므로 에러 수집이 점차 감소했다!

profile
Frontend Developer

0개의 댓글