이전 프로젝트에서는 따로 로깅 시스템을 구축하지 않았다. 따라서 에러가 발생하면 직접 ec2 서버에 접속해 로그를 확인했었는데 많이 번거로웠다. 당시에 로깅 시스템 구축 필요성을 절실히 느꼈고 따라서 이번 프로젝트에서는 CICD를 구축하자마자 바로 로깅 시스템을 구축했다.
운영 중인 웹 애플리케이션이 문제가 발생했을 경우 문제의 원인을 파악하려면 문제가 발생했을 때 당시의 정보가 필요하다. 이런 정보를 얻기 위해서 Exception이 발생했거나 중요 기능이 실행되는 부분에서는 로그를 남겨야 한다.
로깅 vs System.out.println()
로깅을 사용하면 System.out.println() 대비 다음과 같은 이점이 있다.
로깅 vs 디버깅
예외 사항을 가장 잘 파악할 수 있는 것은 디버깅이다. 변수의 값이나, 메모리 주소 등을 breakPoint가 걸린 시점에서 확인할 수 있기 때문이다. 하지만 실제 서버가 구동 중인 상황에서는 디버깅하기에 무리가 있어서 이 경우에 로깅이 최선이다.
로그 레벨
레벨 | 설명 |
---|---|
Fatal | 매우 심각한 에러로 프로그램이 종료되는 경우가 많다 |
Error | 의도하지 않은 에러가 발생한 경우로 프로그램이 종료되진 않는다 |
Warn | 에러가 될 수도 있는 잠재적 가능성이 있는 경우를 뜻한다 |
Info | 요구사항에 따라 시스템 동작을 확인할 때, 명확한 의도가 있는 에러의 경우를 뜻한다 |
Debug | Info 레벨보다 더 자세한 정보가 필요한 경우로 Dev 환경에서 주로 사용한다 |
Trace | Debug 레벨보다 더 자세한 예외 로그를 사용할 때 사용한다 |
Fatal, Error : 시스템상에서 개발자가 의도하지 않은 예외를 나타낼 때 사용
Warn, Info, Debug, Trace : 시스템상에서 개발자가 의도한 예외를 나타낼 때 사용
💡 회원가입 시 DB에 동일한 email을 가진 회원이 있을 때 예외를 던진다면 이 이벤트의 로그는 Info다. 개발자가 의도한 예외이기 때문이다.
Logback은 log를 관리하기 위한 logging framework인 Slf4j(Simple Logging Facade for Java)의 구현체다. 이를 사용하면 구현체의 종류와 상관없이 일관된 로깅 코드를 작성할 수 있으며 구현체를 변경할 때도 최소한의 수정으로 교체할 수 있다. logback은 springboot의 기본 logging framework로 사용될 만큼 범용적이고 다양한 기능을 가지고 있다.
현재 나는 팀 프로젝트를 진행 중이므로 다른 팀원들도 에러 로그를 볼 수 있도록 logback을 slack과 연동해서 사용했다.
slack을 통해 알림을 받으려면 Action-Slack을 활용하면 된다.
Slack API 사이트에 접속한다.
"create new app"을 누르고 본인이 원하는 App Name, development slack workspace를 선택해서 새로운 앱을 만든다.
만든 앱을 선택한 다음 "Incoming webhooks"를 클릭한다.
Incoming Webhooks를 클릭하여 활성화시킨다. 그 후 "Add New Webhook to workspace"를 클릭하여 slack에 내가 원하는 채널과 연동시킨다.
allow를 클릭하면 Webhook URL이 발급된다.
application.yml 파일에 아래 코드를 추가한다.
logging:
slack:
# 발급받은 slack webhook URL을 기입한다.
webhook-uri: [slack webhook url]
config: classpath:logback-spring.xml
Spring boot 애플리케이션 build.gradle에 의존성 추가한다.
implementation "com.github.maricn:logback-slack-appender:1.4.0"
main/resources 하위에 logback-spring.xml 파일을 추가한다.
아래 코드에 대한 궁금하다면 logback-slack-appender 참고하자. 간단하게 설명하자면 아래 설정은 INFO 레벨 이상의 로그 이벤트를 콘솔에 출력하면서, ERROR 레벨의 로그 이벤트를 Slack으로 전송하는 것을 나타낸다. INFO 레벨의 로그는 주로 애플리케이션의 상태 정보를 나타내며, ERROR 레벨의 로그는 중요한 오류 및 예외 정보를 나타낸다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-uri"/>
<appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
<webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg %n</pattern>
</layout>
<username>Log Bot</username>
<iconEmoji>:anger:</iconEmoji>
<colorCoding>true</colorCoding>
</appender>
<!-- Consol appender 설정 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %-5level %logger{35} - %msg%n</Pattern>
</encoder>
</appender>
<appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SLACK"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="ASYNC_SLACK"/>
</root>
</configuration>
예제 컨트롤러 작성 및 API 호출
아래와 같은 연습용 컨트롤러를 작성했다. 그다음 API를 호출해보자.
@RestController
@Slf4j
public class example {
@GetMapping("/ex3")
public void slackErrorSampleController() {
log.info("this log is info");
log.warn("this log is warn");
log.error("this log is error");
}
}
slack에 알림이 전송된다.
이전까지 logback을 활용해 에러 로그를 slack으로 전송해봤다. 사실 간단한 사이드 프로젝트라 logback 만으로도 에러를 처리하는데 있어서 크게 불편한 점은 없었다. 하지만 단순히 slack으로 에러 로그를 전송했기에 로그를 관리하고 모니터링하는데 있어서 한계가 존재했다. 예를 들어서 성능 모니터링이나 각종 지표는 받을 수 없었다. 따라서 에러 로그를 모니터링하는 툴을 알아봤고 Sentry를 도입하기로 했다.
Sentry는 Application Monitoring 도구로 에러로그를 수집하는 데에 특화되었다. 각 코드의 에러들을 모아서 웹에서 확인할 수 있도록 도와주는 플랫폼이다. 상대적으로 저렴하고 다양한 플래폼을 지원하며 구축이 쉬워서 많은 기업들이 실제로 사용하고 있다.
https://sentry.io에 접속해서 회원가입을 한다.
Project 탭에 들어가서 Spring Boot 프로젝트를 생성한다.
Client Keys(DSN)로 들어가서 DSN을 확인한다.
Spring boot 애플리케이션 build.gradle에 의존성 추가한다.
implementation 'io.sentry:sentry-spring-boot-starter-jakarta:6.28.0'
implementation 'io.sentry:sentry-logback:6.28.0'
앞서 작성한 logback-spring.xml 파일에 아래 코드를 추가한다.
<!-- Configure the Sentry appender, overriding the logging threshold to the WARN level -->
<appender name="Sentry" class="io.sentry.logback.SentryAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!-- Optionally add an encoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
다시 api를 호출해보자.
slack으로 error 로그가 전송되었다.
또한 sentry에도 error 로그가 전송되었다. 해당 로그를 클릭하면 더 자세한 지표들을 확인할 수 있다.
번외
위 방식과는 다르게 sentry에서 직접 error 로그를 slack으로 전송할 수도 있다. 아래와 같이 slack에 전송된 로그를 클릭하면 sentry 사이트로 접속해서 즉각적으로 지표들을 확인할 수 있다. 하지만 무료인 위 방식과는 다르게 이 방식은 유료다..