🔬

한국신용데이터가 Sentry를 활용하는 방법

태그
가이드경험공유
최종 편집
Dec 30, 2022 2:32 AM
발행일
October 25, 2021
💡
회사 블로그에 쓰려고 + 사내 공유용으로 만들었는데 이직도 하게 됐고.. 더 편집하는 시간이 아까워서 이대로라도 올려둡니다.

Sentry?

  • 퍼포먼스 모니터링, 유저 피드백 수집 등도 가능하긴 하나, 근본은 에러 모니터링 도구
  • 에러 모니터링에 Sentry가 유명하긴 하나 정답은 아님. 환경에 따라 더 적합한 게 있을 수 있음
    • 서버 / 웹 / 앱
    • React / Vue / Angular
    • SPA / MPA / SDK / Node Package
  • Sentry의 큰 장점이 Breadcrumb인데 이게 다른 툴에 없는 것은 아님
  • 검색해보면 대체재도 많음. 그리고 대체재마다 일반적으로 다른 툴(특히 Sentry)와 비교해서 자기네가 왜 좋은지 설명하기도 함
  • sentry-javascript는 버전이 올라갈수록 패키지 사이즈가 엄청나게 커지고 있어서 불만이 많음

시작하기

  • 플랫폼별로 문서화가 상당히 잘 되어있는 편. React는 여기를 참조
    • 프레임워크 선택해서 프로젝트 새로 만들면 dsn 을 발급받고, 이걸 이용해서 init 이 가능해짐
    • 웹 클라이언트에서는, npm에서 각 프레임워크에 맞는 패키지를 설치해서 사용하면 됨
  • init 할 때 여러 옵션을 선택할 수 있고, integration도 이것저것 있음
    1. export async function initialize({
        dsn,
        allowUrls,
        environment,
        release,
        ignoreErrors = [],
        integrations = [new Integrations.BrowserTracing()],
        tracesSampleRate = 0.1,
      }: BrowserOptions) {
        const IGNORE_ERRORS = (await fetchIgnoreErrors()) as string[];
      
        Sentry.init({
          dsn, // 프로젝트별 유니크 키
          allowUrls, // 어떤 URL에서 오는 에러 리포트만 허용할 것인가
          environment, // develop, staging, production...
          release, // 클라이언트의 릴리즈 버전. Latest git commit hash를 버전으로 사용
          ignoreErrors: [...IGNORE_ERRORS, ...ignoreErrors], // 클라이언트에서 무시할 에러 목록
          integrations,
          tracesSampleRate, // 퍼포먼스 트래킹 비율. 낮을수록 이 측정이 유저에게 미치는 영향이 적고 높을수록 정확함
        });
      
        const appVersionString = appVersion();
      
        if (appVersionString) { // 커스텀 태그도 설정 가능(e.g., 캐시노트 앱 버전)
          Sentry.setTag('appVersion', `${platform()}-${appVersionString}`);
        }
      }
      캐시노트에서 사용하는 Sentry init 코드 예시
    2. release: 직접 설정할 수 있지만, 최대한 자동화하기 위해 KCD 프론트엔드에서는 Latest git commit hash(git rev-parse HEAD)를 버전으로 사용. Github이 public이라면 자동 연결도 가능
    3. // script in package.json: 빌드할 때 환경변수로 commit hash 주입
      "build:production": "REACT_APP_DEPLOY_ENV=production REACT_APP_GIT_SHA=`git rev-parse HEAD` craco build",
      
      // Sentry init할 때 변수로 주입
      initialize({
        dsn: ...,
        allowUrls: [
          /^https:\/\/app2\.cashnote\.kr/,
          /^https:\/\/app2-staging\.cashnote\.kr/,
        ],
        environment: process.env.REACT_APP_DEPLOY_ENV,
        release: process.env.REACT_APP_GIT_SHA,
      });
    4. ignoreErrors: 우리의 노력으로 줄이기 어려운(주로 네트워크와 관련된) 에러는 Sentry에서 ignore 하는 대신 애초에 보내지 않는 게 좋음(그래야 과금되지 않음). KCD는 배포 없이 이러한 에러를 추가/수정할 수 있게 CDN에 올려두고 fetch해와서 사용.
    5. [
        "Failed to fetch",
        "The operation couldn’t be completed. Software caused connection abort",
        "작업을 완료할 수 없습니다. 소프트웨어에 의한 연결 중단",
        "The network connection was lost.",
        "네트워크 연결이 유실되었습니다.",
        "WebKit encountered an internal error",
        "The Internet connection appears to be offline.",
        "Non-Error promise rejection captured with keys: currentTarget, isTrusted, target, type",
        "취소됨",
        "cannot parse response",
        "Network request failed",
        "Network Error",
        "Loading chunk",
        "Loading CSS chunk",
        "Could not connect to the server",
        "An SSL error has occurred",
        "Java exception was raised",
        "checkDomStatus"
      ]
  • 그러면 내 클라이언트에 대한 정보를 Sentry에 어떻게 올리는가? → sentry-cli를 사용
    1. #!/bin/sh
      
      SENTRY_CLI=node_modules/@sentry/cli/sentry-cli
      
      # this exits with code 1 when the given release does not exist
      $SENTRY_CLI releases info $COMMIT_REF --quiet
      
      if [ "$?" == "1" ]
      then
        $SENTRY_CLI releases new $COMMIT_REF
      
        $SENTRY_CLI releases set-commits $COMMIT_REF --commit KoreaCreditData/kestrel@$COMMIT_REF
      
        $SENTRY_CLI releases files $COMMIT_REF upload-sourcemaps --no-rewrite build/static/js
      
        $SENTRY_CLI releases finalize $COMMIT_REF
      fi
      
      $SENTRY_CLI releases deploys $COMMIT_REF new -e $REACT_APP_DEPLOY_ENV -u $DEPLOY_PRIME_URL
    2. 현재 commit association이 잘 안되고, 새 버전이 올라오면 이전 버전의 소스 맵이 없어지는 등 몇가지 문제가 있어서 리서치 및 개선 예정

활용하기

  • 에러 처리의 과정을 러프하게 표현하면
    1. 에러 발생 인지
    2. 에러 빈도, 영향 범위 등을 기반으로 해결 우선순위 설정
    3. 에러 근본 원인 탐색
    4. 해결 후 재배포
  • 여기서 "에러 발생 인지"를 위해 하는 것이 Alerts
    • 일정 시간 안에 몇 명에게 and / or 몇 번 에러가 발생했는지를 기준으로, threshold를 넘으면 Slack으로 리포트하는 식
    • 처음에는 수치를 좀 낮게 잡고, 서비스 성숙도(트래픽)에 따라 수치 조정해가는 게 좋음
    • image
      image
  • Sentry를 통해 모니터링할 수 있는 에러는 크게 두 종류
    • uncaught exception
      • 내 소스코드에 있을 수도 있고
      • 써드파티에 있을 수도 있음 (추적 코드라든가, 앱 웹뷰이면 앱 네이티브가 호출한 무언가라든가)
    • 직접 설정한 에러
      • 유저가 이 경로를 경험할 수 없는데 왔다 → 경로 파악해서 제거
  • 에러 재현하여 근본 원인 파악하기
    • Sentry가 기본으로 넣어주는 여러가지 tag들이 있고, 이에 더해 릴리즈 버전과 커스텀 태그를 이용해서 정확한 '환경'을 우선 파악
    • 그다음 breadcrumb을 관찰하여 유저가 어떤 경로의 행동을 거쳐 이 에러에 도달했는지 확인
    • image
    • API 호출, 네비게이션, UI 요소 클릭 등이 기록되는데, 클라이언트 소스 코드가 익명화되어있거나 등의 이유로 UI 클릭을 봐도 뭘 했다는 건지 모를 수 있음
    • Amplitude User Lookup이나 서버 로그 등의 도움을 더 받아야 할 수도 있음
    • image
  • KCD 프론트엔드 팀에서 에러를 발견했을 때 처리하는 대략적인 방법
    • (2주 단위로 운영업무 담당이 바뀜)
    • 슬랙 채널에 리포트가 오면 원인 파악
      • 무시해도 되는 에러인지, 스테이징 환경에서 테스트하다가 나온 것인지, 브라우저 특성 때문에 폴리필이 필요한지 등
      • 컨텍스트 파악이 추가로 필요하면 서버/클라이언트 개발 리드들, 고인물들 핑
    • 무시해도 되는 에러면 ignore_errors 에 추가
    • 원인을 파악하기 위해 좀 더 패턴이 필요한 것이면 "n회 더 발생할때까지 ignore" 같은 걸 찍어둠
    • 원인도 알고 해결해야 하는 것이면 PR 날림
      • 영향 범위에 따라 즉시 해결할 것인지 나중에 볼 것인지 결정하긴 하나, 기본적으로 운영업무 담당은 메인 업무와 에러 탐색/해결을 7:3 정도의 비중으로 시간을 쓰는듯
  • Some Tips
    • 내가 이 에러를 인지하고 싶은가? 인지하면 고칠 수 있는가? 를 생각해야 함
      • 예를 들어 서버 API로부터 500 에러가 떨어졌는데 이걸 클라이언트가 잘 핸들링하고 있다면, 서버 Sentry에는 에러가 뜨고 클라이언트에는 안 뜰 수도 있음. → 이 상황을 클라이언트에서도 에러로 판단해서 Sentry에 리포트할 것인가는 팀 안에서 판단해야 함
    • 가장 자주 나오는 에러는 null/undefined reference 에러들 줄이는 방법
      • 클라에서는 TypeScript 쓰면 거의 해결되고
      • API 받아온 결과값이 예상과 다를 때에도 자주 발생하는데, OpenAPI Generator 따위를 통해 API 스키마를 TypeScript type 등으로 만들어놔서, 개발 단계에서 핸들링할 수 있도록 하면 좋음