개발툴

Serverless + Next.js에 Sentry 도입하기

kicksky 2022. 6. 30. 01:17

제목 거창해서 웃기다. 사실상 센트리 도입 과정의 삽질 여정에 대한 기록인데(...). 센트리를 여러 회사에서 도입해서 사용하는 것 같은데 막상 공식 홈페이지의 가이드나 문서는 다국어 지원이 안 보여서 다들 어떻게 쓰고 있는건지 좀 의아했다. 나만 좀 헤맸나 싶었다. 어쨌든 누군가에게는 도움이 되지 않겠나 싶어 기록을 남기기로 했고, 사실상 환경 설정 과 관련된 번역 위주의 설명이 될 것 같다.

배경

우선, 센트리를 도입한 배경. 우리 팀은 Serverless 환경에 배포되고 실행되는 Next.js 기반의 프로젝트를 운영하고 있고, 특별한 계기가 있던 건 아니지만 프로덕트를 성장시키려면 좀 더 체계적으로 관리할 수 있는 조건을 만들어 놓아야 할 필요성을 느껴 팀 내에서 센트리를 한 번 써보자는 결론이 나왔다.

Next SDK

Sentry Next.js 문서

심플한데 안 심플함

센트리 홈페이지에 가면 지원되는 언어와 프레임워크가 엄청 많다는 걸 알 수 있다. Next를 쓰고 있으니까 이 문서만 보면 되겠지 했는데 결국 javascript, node, react, next 관련 페이지를 들락거리며 다 훑어봤던 것 같다. Next.js SDK가 React SDK + Node SDK으로 구성된 터라 커스텀 설정 등을 찾다가 이것저것 봐야했다. (나만 헤맨 걸수도)

설치는 npm으로 간단하게 해주면 되고, 세부 설정은 센트리에서 제공하는 설정 위저드를 불러오면 npx @sentry/wizard -i nextjs 알아서 필요한 파일들을 설치해주고 세팅해주는데 ... 개인 프로젝트가 아니라면, CI/CD를 고려한다면 결국 수동으로 직접 하는 게 낫다. manual setup으로 가자.

직접 설정

우선, 루트 경로에 센트리 초기화 코드인 sentry.client.config.jssentry.server.config.js 이 있어야 한다.

custom _error page

custom _error page에 대한 설명도 있다. 서버리스 배포 환경(vercel 포함)에서 nextjs는 서버리스 함수 사이즈를 줄이려고 “minimal” 모드에서 서버를 실행한다. 그 결과 sentry/nextjs가 auto-instrumentation을 일부 실행할 수 없어서 놓치는 에러들이 있다. nextjs는 센트리 핸들러까지 버블 업 되기 전에 특정한 에러들을 잡을 수 있도록 custom error boundary를 포함할 수 있다. 이 부분은 pages/_error.js를 센트리에서 제시하는 코드를 참고하여 작성하면 된다.

Next.js 추가 설정

// next.config.js
...
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);

next.config.js 파일은 next의 웹팩 설정 파일인데 센트리를 사용하려면 이 설정을 withSentryConfig으로 감싸주어야 한다. 이 withSentryConfig이 중요하다. 내가 헤맸던 가장 큰 이유가 이 withSentryConfig의 역할프로덕트에 영향을 끼치는 시점을 정확하게 파악하지 못해서 였다. 이 설정은 파일을 빌드할 때 ! 적용되고 (그것이 웹팩의 주요 목적이니까...), 빌드한 파일을 센트리 서버에 업로드 ! 한다.

  • 앞서 말했듯 센트리를 initialize 하는 코드가 포함된 sentry.client.config.js, sentry.server.config.js 두 파일을 각각 서버가 구동되기 시작할 때, 클라이언트 페이지가 로드될 때 자동으로 호출한다. 
  • 이로써 SDK가 모든 에러를 잡고 퍼포먼스 모니터링을 시작할 수 있는 가장 이른 시점에 센트리가 초기화된다.
  • 소스맵을 생성하고 이를 센트리 서버에 업로드한다. 그러면 스택트레이스에 원본 코드 파일이 포함된다. (에러가 발생했을 때, 센트리에서 해당 에러가 발생한 원본 코드를 확인할 수 있다는 것)

센트리 예시 코드에는 로그를 숨기는 silent만 true로 설정되어 있다. 나머지 기본값들은 가급적 건드리지 말라고 함.

이래저래 제대로 빌드되어 센트리 서버에 해당 파일들이 잘 올라갔다면 sentry.io에서 확인할 수 있다.

서버리스 주의사항

서버리스 환경에서는 주의할 점이 한 가지 더 있다. 서버리스 환경에서 SDK 사용하려면 next 빌드 설정인 target을 serverless가 아니라 experimental-serverless-trace (nextjs 10, 11) 로 해주어야 한다. (next 8에서는 next.config.js에 빌드 타겟을 'serverless'로 설정할 수 있었다. 그 이후에는 빠진 것으로 알고 있다. 참고) 이와 관련해서 검색하다가 깃헙 레포의 이슈에서 레포 메인테이너의 흥미로운 답변을 발견해서 간단히 정리하자면, 

사람들이 target: ‘serverless’로 배포해놓고 일반 서버에서 next start로 실행시키면 퍼포먼스가 향상될 줄 아는데 오히려 감소한다. 그럴 의도로 만든 게 아니기 때문이다. 그리고 몇년 간 지켜보니까 serverless target이 문제가 좀 있었고, Vercel 같은 데서 배포할 때는 아예 experimental-serverless-trace로 대체된다. 지금 여전히 타겟으로 serverless를 쓰는 프로젝트는 serverless-next 뿐인데 현재로서는 compatibility 문제도 있으니까 이 target을 없앨 생각은 없다. 그치만 문서에 남겨놓으면 유저들한테 혼란만 가중시키고 별 가치도 없어서 삭제해놓은 상태다.

타겟을 serverless로 설정해놓으면 현재 Next 기능들인 preview mode, revalidate, fallback 같은 건 쓸 수 없다고 한다. 아무튼 다시 돌아와서 우리 프로젝트는 serverless.yml에서 서버리스 배포 환경 설정을 해주고 있었으므로 해당 파일에

inputs:
	useServerlessTraceTarget: true

이렇게 추가해주었다. (처음에 inputs이랑 같은 계층에 저 설정값 넣었다가 계속 오류났다. inputs의 내부 값으로 들어가야한다.)

소스맵 설정

앞서 말한 withSentryConfig은 클라이언트 빌드, 서버 빌드 두 경우에 모두 기본적으로 SentryWebpackPlugIn 인스턴스를 웹팩 플러그인에 추가한다. (솔직히 withSentryConfig 편하긴 한데 너무 알아서 다 하려다 보니까 조금이라도 커스텀하려고 하면 결국 돌아가는 로직을 다 까봐야한다. 이 부분 때문에 결국 javascript, react 문서까지 다 찾아본 것) 즉, 프로덕션 빌드(next build)를 실행하면 sentry-cli가 자동으로 소스 파일, 소스맵, 번들 등을 찾아내서 센트리에 업로드한다. 앞서 말했듯이 이렇게 하면 에러에 대한 stacktrace가 원본 코드를 확인할 수 있다. 

소스맵이 프로덕션에 같이 올라가서 원본 코드가 브라우저에서 노출되는 것을 막으려면 hideSourceMaps를 true로 설정해주면 된다. 소스맵 생성 여부 및 방법을 제어하는 웹팩의 devtool 옵션 중에서 ‘hidden-source-map’ (참조가 없으며, 에러 보고 목적으로 소스맵을 사용할 때 선택 할 수 있습니다.)에 해당한다. 내 개발 환경으로 설명하자면 빌드할 때 *.js, *.map 파일들이 생성되고 이것들이 센트리 서버에 업로드 되지만, 최종적으로 배포될 때에는 S3 버킷에 *.map 파일들이 제외된 채로 업로드 된다. 그러면 브라우저에서 해당 원본 코드가 노출되지 않는다.

const moduleExports = {
  sentry: {
    hideSourceMaps: true,
  },
};

Release

센트리에는 Release라는 프로세스가 있다. 직접적으로는 environment에 배포된 코드의 버전을 의미한다. 이 과정이 중요한 근본적인 이유는 최소화 되어서 쉽게 읽을 수 없는 JS에 소스맵을 매칭 시켜서 오리지널, 변형되지 않은 소스코드를 보는 데 쓰이기 때문이다. 앞서 센트리에 업로드된 파일들을 버저닝하고, 에러가 발생했을 때 업로드된 코드들과 매칭시키는 일종의 아이디 같은 것으로 이해했다. 그 외에도

  • 새로운 릴리즈에 포함된 issue, regression 확인할 수 있고,
  • 어떤 커밋이 이슈 발생시키고 누구 책임인지 예상 가능하며,
  • 커밋 메세지에 이슈 넘버 포함시키면 이슈가 해결 resolve 된 상태로 처리되고,
  • 코드가 배포될 때 이메일 알림 받을 수 있다.

나의 가장 큰 실수는 이 Release 단계에서 센트리에 코드 파일과 소스맵이 업로드 된다고 생각했던 것이었다. (스스로를 매우 쳤음) 별개다. 빌드와 센트리 서버에 코드 및 소스맵 업로드는 withSentryConfig 플러그인이 담당하고, Release는 말 그대로 릴리즈, 버저닝, 아이디화라고 보면 될 것 같다. 

우리 팀의 경우는 Github Actions의 workflow로 CI/CD를 자동화 했기 때문에 github actions marketplace에서 쉽게 Sentry Release 액션을 가져와서 추가해주었다. 기본 version 식별자는 github.sha로 지정된다. 따로 프로덕트 릴리즈 버전을 관리한다면 수동으로 설정할 수 있다. 그리고 해당 액션에 넘겨주어야 하는 환경변수 값으로 TOKEN이 필요한데 이건 settings -> developer settings -> create new integration 에서 새로 팀 GitHub Actions Release Integration을 생성해서 발급 받으면 된다 .

마무리

사실 아직 궁금한 게 몇 가지 남았는데 우선 이렇게 정리해놓고 넘어가려고 한다. 그것마저 해결되면 또 업데이트 될 수도. 센트리 남들은 다들 기본으로 후딱 도입하던데 나는 결국 serverless framework 코드까지 까보고 이래저래 삽질이 깊었다.