숨겨진 버그를 찾고 유저 리포트에 의존하지 않는 구조 만들기 (Sentry)


우리는 웹과 앱을 동시에 운영하면서, 서비스 안정성을 유지하는 것이 얼마나 어려운 일인지 점점 더 체감하게 되었다. 기능은 빠르게 추가되고, 배포 주기는 짧아졌지만, 그만큼 예상하지 못한 오류가 발생할 가능성도 함께 커졌다.

그동안 오류를 인지하는 방식은 꽤 수동적이었다. 대부분의 문제는 유저가 먼저 불편을 느끼고, 그 다음에야 Discord나 DM을 통해 문제가 있다고 알려주는 구조였다.

이 방식에는 명확한 한계가 있었다.
우리는 항상 문제가 발생한 뒤에야 움직일 수 있었고, 유저 입장에서는 굳이 시간을 들여 문제를 설명해야 했다.

나는 Sentry를 도입해서 유저가 말해주기 전에, 우리가 먼저 문제를 알아차릴 수 있는 구조를 만들기로 하였다.


우선 Sentry를 도입하게 된 가장 큰 이유는 유저가 직접 리포트하기 전에 우리가 문제를 파악하고 해결해야 했기 때문이다.

유저가 오류를 경험했다는 것은 이미 신뢰가 한 번 손상되었으며 경우에 따라서는 다시 돌아오지 않을 수도 있다는 의미이다.
이 시점에서 우리가 할 수 있는 일은 사과하고, 수정하고, 재발 방지를 약속하는 것뿐이다.

Sentry를 도입하면, 배포 직후 어떤 에러가 발생했는지 특정 환경이나 브라우저에서만 터지는 문제는 없는지 실제로 몇 명의 유저가 영향을 받고 있는지를 유저의 제보 없이도 바로 확인할 수 있다.


그리고 눈에 보이지 않는 버그가 생각보다 훨씬 많다.

모든 버그가 화면을 깨뜨리거나, 앱을 바로 크래시 내지는 않는다.
오히려 더 위험한 것은 조용히 실패하는 버그였다.

특정 API 호출이 실패하지만 UI에는 아무 표시가 없는 경우나 일부 상태값이 꼬여서 특정 기능만 동작하지 않는 경우, 그리고 특정 유저, 특정 기기, 특정 타이밍에서만 발생하는 오류가 이에 해당한다.

이런 문제들은 QA나 내부 테스트로는 거의 잡히지 않는다.
그리고 유저 역시 이게 버그인지 아닌지 확신하지 못한 채 그냥 넘어가는 경우가 많다.

Sentry는 이런 눈에 보이지 않는 오류들을 stack trace, 발생 위치, 사용자 컨텍스트와 함께 기록해준다.


마지막 이유는 사용자 경험과 직접적으로 연결된 문제였는데, 앱 안에서 바로 리포트할 수 있는 체계가 필요했다.

기존에는 문제가 발생하면, Discord에 들어와서 상황을 설명하고 스크린샷을 올리고 다시 답변을 기다리는 과정을 거쳐야 했다.

개발자 입장에서는 편한 흐름이지만, 일반 사용자에게 이 과정은 너무 귀찮고 번거롭다.
결과적으로 많은 문제들은 리포트조차 되지 않은 채 사라진다.

Sentry는 유저가 직접 남긴 코멘트를 User Feedback 에서 볼 수 있도록 할 수 있기 때문에 앱 안에서 바로 오류를 리포트할 수 있는 구조를 만들고자 하였다.


1. 전역 Error Boundary로 클라이언트 에러를 Sentry로 Capturing 하기

Next.js(App Router) 환경에서는 error.tsx를 통해 전역 에러 바운더리를 구성할 수 있다.
클라이언트 에러가 발생하면 이 바운더리에서 캐치해서 Sentry로 에러를 보내는 플로우를 작성하였다.

대부분의 예상하지 못 한 에러는 여기에서 처리가 된다.

TSX
'use client'

import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'

export default function GlobalError({ error, reset }) {
  useEffect(() => {
    Sentry.captureException(error, {
      tags: {
        source: 'application',
        errorBoundary: 'error.tsx',
      },
      contexts: {
        error: {
          digest: error.digest,
        },
      },
    })
  }, [error])  

  ...
}

전역 에러 바운더리에서 캡쳐된 오류는 Sentry Dashboard의 Erros & Outages 에서 바로 확인할 수 있다.
메일로도 날라오도록 설정해두어서 즉각즉각 대처가 가능하다.


에러는 아래와 같이 어떤 환경에서 일어났는지, 언제 일어났는지 등 여러 데이터도 함께 로깅된다.

Trace도 매우 자세하게 기록되어서 문제가 된 지점을 매우 자세하게 짚어낼 수 있다.

또한 리플레이까지 볼 수 있어서 Mixpanel Session Recording이랑 함께 활용하면 더 높은 시너지를 낼 수 있다.

Production 환경은 번들링 된 코드가 올라가서 모든 코드가 난독화되어 있는데, 설정을 통해 sourcemap을 활용해서 원래 코드로 오류 위치를 정확히 특정할 수 있다.

이 기능 덕분에 디버깅이 굉장히 쉬워지고, 많은 이슈에 빠른 대처를 할 수 있었다.

그리고 Seer 라는 기능도 제공하는데, 문제에 대한 원인을 AI가 분석해주는 아주 좋은 기능이다.

모든 오류가 내 소스코드에서 발생하는 것도 아니고, Native와도 복잡하게 엮여서 발생하는 경우가 많기 때문에 단순 Trace만 보고 오류를 해결하기 어려울 때가 있다.

그럴 때는 Seer에게 맡겨보는 것도 좋은 것 같다.
브랜치와 연동되어 소스 코드를 직접 참조해서 분석해주기 때문에 꽤나 좋은 Insight를 얻을 때가 많다.


2. Feedback 모달로 앱 안에서 바로 버그 리포트하기

앱 내에서 Feedback을 할 수 있는 시스템을 사실 필수이다.
우리는 원래 Zendesk로 관리하고 있었는데, Sentry를 도입하면서 바꾼 것이다.

Zendesk는 자체 UI로 피드백을 보낼 수가 있는데, Sentry 도입 후 Feedback 모달을 새로 만들었다.

앱 곳곳에 Send Feedback 같은 이름으로 배치해서 바로 피드백을 보낼 수 있도록 하였다.
또한, 채팅 API 중 오류가 나면 앱 상단에 Notification이 뜨면서 클릭 시 바로 버그 리포트를 할 수 있도록 추가 처리를 해두었다.

위 피드백 폼에서 제출한 에러는 바로 Sentry의 User Feedback에서 볼 수 있고, 이메일로도 알림을 보내준다.


그리고 우리는 Sentry를 이벤트를 기록하는 도구로도 활용하고 있다.
특히 마케팅 SDK 연동 영역에서 이 패턴이 유용했다.

TypeScript
Sentry.withScope(scope => {
  scope.setTag('source', 'appsflyer')
  scope.setTag('appsflyer_event', 'GOOGLE_ADS_MISSING_AF_DP')
  scope.setContext('google_ads_fallback', {
    media_source: data.media_source,
    campaign: data.campaign,
    af_status: data.af_status,
    is_first_launch: data.is_first_launch,
    original_af_dp: null,
    fallback_af_dp: data.af_dp,
    timestamp: new Date().toISOString(),
  })
  Sentry.captureMessage(
    `[AppsFlyer] Google Ads missing af_dp - ${data.af_dp}`,
    { level: 'warning' },
  )
})

이렇게 하면, 에러는 아니지만 정상적이지 않은 상황, 나중에 분석이 필요할 수 있는 분기, 마케팅 퍼널에서 발생한 이상 케이스를 맥락(Context)과 함께 기록할 수 있다.

  • 물론 Mixpanel에도 마케팅 관련 트래킹이 되고 있다.


Sentry는 더 크래시 로그만 모으는 곳이 아니라 운영 중 발생하는 이상 신호를 모으는 역할도 하게 되었다.


Sentry 도입을 한 뒤에 리포트 된 이슈 외에 보이지 않는 이슈가 상당히 많다는 것을 확인하였다.

또한 유저가 불편을 느끼고 나서야 문제를 알게 되는 구조에서 벗어나, 문제가 발생하는 순간 이미 우리가 인지하고 대응할 수 있는 상태를 만들 수 있었다.

생각보다 보이지 않는 이슈가 많았는데, 프론트를 혼자 맡고 있는 상황에서 모든 이슈를 세부적으로 파악하고 해결하는 것은 또 하나의 과제가 되었다.

그렇다고 하더라도 문제를 더 빨리 발견하게 되었고 서비스 신뢰도를 지킬 수 있어서 성공적인 프로젝트였다고 생각한다.

댓글 남기기

Dalmeng's Footprints에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기