Sentry
Sentry란
개발이 완료된 서버를 배포한 운영 상황에서 로깅과 모니터링의 중요성은 논란의 여지 없이 매우 매우 매우 중요하다.
만약 이러한 에러 로깅과 모니터링을 동시에 할 수 있는 도구가 있다고 하면 믿겠는가?
그것이 바로 Sentry이다!
Sentry의 강점
Sentry는 실시간으로 프로그램 이슈를 트래킹하다가 에러가 발생하면 설정에 따라 특정 사용자나 그룹에 알림을 보낼 수도 있다.
만약 Slack이나 카카오 워크(깨알 홍보)같은 팀내 협업 도구를 사용한다면 해당 채팅방으로 알람을 쏴주는 것도 가능하다.
카카오에서는 이러한 시스템들이 잘 구축되어 있어서 Sentry가 해당 관리 서비스의 DSN만 명시해주면, 알아서 카카오톡에서 알람을 날려주는 Bot이 존재한다.
Sentry의 또 다른 강점은 Java, Golang, Node, Python 등 거의 모든 언어 플랫폼을 지원하며, Unreal Engine같은 게임 개발 도구까지도 지원된다.
즉, 방대한 플랫폼 지원이 Sentry의 또 다른 강점이기도 하다.
Springboot에 센트리 적용해보기
센트리 설정은 그렇게 어렵지 않다.
우선 예제를 보자.
스프링 부트 2.3.0으로 작성되었고 Kotlin으로 작성되었음을 미리 알린다.
@Configuration
class BeanConfig {
@Bean
fun sentryExceptionResolver(): HandlerExceptionResolver {
return SentryExceptionResolver()
}
@Bean
fun sentryServletContextInitializer(): ServletContextInitializer {
return SentryServletContextInitializer()
}
}
HandlerExceptionResolver 인터페이스를 구현하고 있는 것을 볼 수 있다.
HandlerExceptionResolver는 DispatcherServlet
이후 발생하는 모든 예외 상황을 처리하는 Resolver라고 보면 된다.
ServletContextInitializer을 처음 봐서 조금 조사를 해보았다.
* This interface is designed to act in a similar way to
* {@link ServletContainerInitializer}, but have a lifecycle that's managed by Spring and
* not the Servlet container.
ServletContextInitializer를 열어보면 위와 같은 주석이 달려있는데, 대략 내용은 다음과 같다.
이 인터페이스는 ServletContainerInitializer과 비슷한 동작을 수행하기 위해 디자인되었지만,
서블릿 컨테이너가 아닌 스프링에 의해 관리되어지는 라이프 사이클을 갖고 있다.
여기서 알 수 있는 사실은 ServletContainerInitializer은 서블릿 컨테이너에 의해 관리되는 생명주기를 갖는 다는 것을 알 수 있다.
그도 그럴 것이 ServletContainerInitializer는 이름에서 보듯이 서블릿 컨테이너를 구동하는 인터페이스이다.
그래서 startUp이라는 메소드 하나만 갖는 것을 알 수 있다.
Spring MVC에서는 ServletContainerInitializer를 구현한 SpringServletContainerInitializer를 제공하고 있다는 사실을 잊지말자.
Sentry 공식문서를 살펴보면 스프링부트의 경우 javax.servlet.ServletContainerInitializer를 자동으로 로드하지 않는다고 한다.
왜 스프링부트는 MVC의 javax.servlet.ServletContainerInitializer를 자동으로 로드하지 않는걸까?
스프링부트가 javax.servlet.ServletContainerInitializer를 자동 로드하지 않는 이유
이유는 스프링 공식 문서에 다음과 같이 나와있다.
- Servlet Context Initialization -
Embedded servlet containers do not directly execute the Servlet 3.0+ javax.servlet.ServletContainerInitializer interface or Spring’s org.springframework.web.WebApplicationInitializer interface. This is an intentional design decision intended to reduce the risk that third party libraries designed to run inside a war may break Spring Boot applications.
If you need to perform servlet context initialization in a Spring Boot application, you should register a bean that implements the org.springframework.boot.web.servlet.ServletContextInitializer interface. The single onStartup method provides access to the ServletContext and, if necessary, can easily be used as an adapter to an existing WebApplicationInitializer
내용을 요약하자면,
스프링부트의 내장 서블릿 컨테이너가 직접적으로 javax.servlet.ServletContainerInitializer를 실행하지 않는 이유에 대해서 명시하고 있다.
그 이유는, war 내부에서 실행되는 써드 파티 라이브러리가 스프링 부트 애플리케이션을 종료시키는 위험을 줄이기 위해 의도적으로 디자인되었기 때문이다.
즉, 스프링은 javax.servlet.ServletContainerInitializer가 직접적으로 실행되면 써드파티 라이브러리에 의해 스프링 부트 애플리케이션에 영향을 줄수도 있다고 생각하여 이를 실행하지 않았고,
대신 이를 대체하기 위해 org.springframework.boot.web.servlet.ServletContextInitializer interface를 빈으로 등록하면 servlet context initialization을 수행할 수 있다고 한다.
그래서 스프링 부트에서는 javax.servlet.ServletContainerInitializer가 servlet context initialization의 목적으로 상용되기 보다는 org.springframework.boot.web.servlet.ServletContextInitializer의 사용을 권장한다는 사실을 알 수 있다.
이러한 의도를 반영해서 Sentry 또한 io.sentry.spring.SentryServletContextInitializer를 빈으로 등록해서 사용하고 있다.
io.sentry.spring.SentryServletContextInitializer의 경우 이름에도 알 수 있듯이, ServletContextInitializer의 구현체이다.
스프링 부트에서 ServletContainerInitializer를 직접 실행할 수 없기 때문에 servlet context initialization이 필요하다면 ServletContextInitializer를 사용해야 한다.
그 이유는 HTTP Request Data를 가져오기 위해 서블릿 컨테이너의 컨텍스트를 얻어와야하는데 스프링부트에서는 ServletContainerInitializer를 자동으로 로드해주지 않기 때문에
ServletContextInitializer를 빈으로 등록해서 사용하는 방식을 권장하고 있다.
Reference
https://johnmarc.tistory.com/51
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/