
밍글을 개발하며 exception 모니터링을 드디어 구축하기로 했다.
강의평가와 시간표 등 새로운 기능들도 곧 출시하고 AWS의 Cloudwatch로 로그를 보았었는데 현실적으로 에러 모니터링을 하기엔 불가능해 이번에 Sentry를 도입하게 되었다.
도입해보고 느낀 장점은
1. 연동이 쉽다.
2. 따로 설정하지 않아도 예외 발생시 이메일이 온다.
3. Logback 등 로깅 프레임워크와도 연동해 customization이 가능하다.
정도가 있다.
절차를 요약하자면 다음과 같다.
1. Sentry가입, 프로젝트 생성 및 DSN 발급
2. build.gradle로 Sentry SDK 설치
3. configuration 작성
처음에 접속하고 Create Project를 누른 후 플랫폼만 선택하면 친절하게 가이드를 해준다.

플랫폼을 선택한 후 넘어가면 아래와 같이 설치를 하라는 가이드가 나오는데, 우리는 Gradle Plugin으로 설치를 할거기때문에 해당 설정은 스킵하면 된다.
그리고 DSN은 자동으로 이 절차에서 생성이 된다. 이는 exception 이벤트를 어디로 보내야 할지에 대한 정보를 담고있다.
Sentry는 애플리케이션의 런타임 내에서 SDK를 사용하여 데이터를 캡처한다.
build.gradle에서 아래와 같이 설치할 수 있다.
implementation 'io.sentry:sentry-spring-boot-starter-jakarta:7.9.0'
공식 문서를 보면
plugins {
id "io.sentry.jvm.gradle" version "4.5.1"
}
위와 같이 Gradle plugin으로 설치하는걸 권장하는데, 모종의 이유로 최신 버전인 7.9.0이 아닌 7.8.0으로 설치되길래 처음과 같이 직접 설치해주었다.
따로 Configuration 클래스를 작성해도 되지만 기본적인 설정만 필요하기도 하고 편의상 application.yml에 작성해주었다.
다양한 configuration option이 있는데 그중에 필수로 설정해야 하는것은 dsn이며, 나머지 설정은 요구사항에 맞게 추가하였다.
DSN은 환경변수로 주입해주었고, prod profile일때만 모니터링하도록 프로필도 적용시켜주었다.
작성한 최종 yml은 아래와 같다.
---
spring:
config:
activate:
on-profile: prod
sentry:
dsn: "${SENTRY_DSN}"
exception-resolver-order: -2147483647
max-request-body-size: always
send-default-pii: true
traces-sample-rate: 1.0
다음은 각 설정에 대한 설명이다.
dsn: DSN은 SDK에게 예외 이벤트를 어디로 보내야 할지에 대한 정보를 담고있다. 만약 DSN을 적지 않는다면, SDK는 SENTRY_DSN 이라는 environment variable에서 해당 정보를 가져오려고 시도한다.
따라서 프로젝트에 발급받은 고유 dsn만 적어주면 기본적인 설정은 다 끝난다.
해당 값은 public으로 private한 값은 없어 노출되어도 된다고는 하나, 해당 dsn으로 누군가가 이벤트를 보낼 수 있는 위험은 있기 때문에 aws secret manager를 통해 넣어주었다.
exception-resolver-order : @ExceptionHandler를 사용할 시 설정.
Sentry에서는 디폴트로 unhandled exceptions만 sentry로 전송된다.
하지만 우리 프로젝트에서는 @RestControllerAdvice로 글로벌하게 @ExceptionHandler로 에러를 잡아서 처리해주기 때문에 해당 프로퍼티를 설정해주지 않으면 Sentry에 전송되지 않는다.
따라서 해당 값을 -2147483647 (org.springframework.core.Ordered#HIGHEST_PRECEDENCE 값) 으로 설정해주면 @ExceptionHandler로 핸들되는 예외들도 Sentry로 보낼 수 있다.
max-request-body-size : HTTP requestBody를 캡쳐하고싶을 때 사용.
크기에 따라 설정을 바꿀 수 있다. 아래 4가지 옵션을 참고하자.
never: Request bodies are never sent.
small: Only small request bodies will be captured. The cutoff for small depends on the SDK (typically 4KB).
medium: Medium and small requests will be captured (typically 10KB).
always: The SDK will always capture the request body as long as Sentry can make sense of it.
sendDefaultPii : RequestBody를 잡고싶을때 사용
boolean flag로, default로는 꺼져있지만 활성화하기 되면 특정 personal identifiable information이 이벤트에 추가로 잡힌다. 나같은 경우에는 requestBody를 캡쳐하기 위해서 해당 설정도 enable 해야했고, 설정을 하니 Authorization 헤더도 잡혀 토큰도 같이 수집되었다.
밍글에서는 토큰의 memberId를 넣는데, 밍글은 익명 커뮤니티기에 수집하는 유저 식별 정보가 암호화된 email 뿐이다. 따라서 토큰이 노출되어도 memberId로 알 수 있는 정보가 없기 때문에 따로 수집 대상에서 제외하기 위한 설정을 하진 않았다. 하지만, 어떤 데이터를 sentry에 보내고 싶지 않은지 (data scrubbing이라고 한다) 수동으로 자세하게 설정할 수 있다.
해당 설정은 이 링크를 참고하자.
trace-sample-rate : performance monitoring을 위해 Sentry 트랜잭션 수를 100% 캡쳐하려면 1.0으로 설정해주면 된다.
Sentry에서 transaction이란 페이지 로드, 페이지 탐색 또는 비동기 작업과 같이 측정하거나 추적하려는 작업의 단일 인스턴스를 나타낸다고 하는데, 자세한 내용은 이 링크를 참고!
공식문서를 보면
import io.sentry.Sentry;
try {
throw new Exception("This is a test.");
} catch (Exception e) {
Sentry.captureException(e);
}
configuration을 완료한 후 위와 같이 verify(테스트)하라는 코드가 예시로 주어지는데, 이와 같이 매번 에러가 날 때 마다 try catch로 잡아서 Sentry에 보낼필요는 없다. Sentry는 모든 unhandled exception을 잡아주는데 위에 yml에서 exception-resolver-order 설정까지 해주었다면 handled되더라도 모든 예외를 잡아서 보낼 수 있기 때문!
적용을 마치면 Issues 탭에서 위와 같이 exception들을 볼 수 있다!
기본적인 설정만 할거라면 yml설정만 해줘도 편리하게 적용할 수 있기 때문에 다들 적용하는것을 추천한다!
참고자료
https://docs.sentry.io/platforms/java/guides/spring-boot/#install
https://docs.sentry.io/concepts/key-terms/dsn-explainer/#what-the-dsn-does
https://docs.sentry.io/platforms/java/guides/spring/configuration/options/#common-options
https://docs.sentry.io/platforms/java/guides/spring-boot/configuration/sampling/#sampling-error-events
https://github.com/getsentry/sentry-java/issues/331#issuecomment-789314045
https://github.com/getsentry/sentry-java/issues/3358
https://docs.sentry.io/platforms/java/guides/spring/configuration/options/#in-app-exclude