스프링부트 LogBack
🔥 문제 의식
로그 관리를 효율적으로 해야겠다는 생각이 든 건 라이온하트 서버를 개발하는 과정에서였다.
서버 로직을 개발할 땐 크게 문제될 상황이 없었지만, 클라이언트 측에서 API를 연결하는 과정에서
로그를 확인해야하는 경우가 많이 발생했는데 로그 파일 하나에 모든 로그를 전부 기록하여 관리하다보니
1. 원하는 정보를 찾기 굉장히 어려웠고 정리되지 않은 로그는 이해하기 굉장히 어려웠고
2. 로그가 계속 쌓여 차지하는 용량이 끊임없이 증가했다.
이러한 이유에 의해 로그를 체계적으로, 그리고 효율적으로 관리하기 위한 방법을 생각했고 LogBack이라는 것을 알게 되어 학습 및 적용해보았다.
그리고 미래의 나를 위해 그 과정을 간단히 기록해보고자 한다.
📝 로깅을 하는 이유
로깅이란 시스템이 동작할 때 시스템의 상태 및 동작 정보를 시간 경과에 따라 기록하는 것을 의미한다. 로깅을 통해 개발자는 개발 과정 혹은 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진달할 수 있고, 다양한 정보를 수집할 수 있다. 사용자 로그의 경우 분석 데이터로 활용할 수 있다. 하지만 로깅을 하는 단계에서 적절한 수준의 로그 기록 기준을 잡지 못하면 방대한 양의 로그 파일이 생성되는 문제를 겪거나, 의미 있는 로그를 쌓지 못하는 경우가 발생할 수 있다. 결국 효율적으로 로깅을 하는 방법을 이해하는 것이 중요하다.
By https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/
가장 많이 사용되는 대표적인 로거는
- Logback
- Log4j2
위와 같이 2가지 Logger가 존재하는데,
LogBack이 현재 프로젝트에서 사용하기에 가장 적절했기 때문에 LogBack을 선택했다.
Logback은 Spring 기본인 Slf4j의 구현체이기 때문에 특별한 설정없이 사용할 수 있었다.
반면 Log4j2는 기본 설정이 아니라 이를 사용하기 위해 추가적인 설정이 필요했다.
Log4j2와의 큰 차이는 Log4j2는 비동기 로깅 성능이 Slf4j보다 월등히 뛰어나다는 점인데, 위 자료에서 알 수 있듯이 Logback에 비해 쓰레드 당 로깅 수가 훨씬 크다는 걸 확인할 수 있다.
하지만 나는 현재 비동기로 로그를 출력하는 부분이 없을 뿐더러, 이후에 비동기 로그로 사용할 예정인 부분도 Slack 5xx에러 로깅뿐이다.
5xx에러 로깅은 자주 발생하는 것이 아니기 때문에, 해당 로깅의 성능하나 올리자고 기본 설정을 뜯어 Log4j2를 붙이는 건 비효율적이라고 생각했다.
🤔 그냥 System.out.println() 을 사용하면 안되는걸까?
라는 생각을 했지만, 아래 이유 때문에 하지 않았다.
- 추적할 수 있는 정보가 적다 (Thread, time 등)
- 로그 레벨 설정 불가능 (로그 레벨을 통해 필요한 로그만 분리하여 관리할 수 없다)
- 로깅 성능 저하 (Println은 synchronized로 처리)
3번째 이유로 소개한 System.out.println 메서드의 내부이다.
내부에서 실제 쓰기 동작은 writeln으로 처리되는데 해당 메서드 내부의 동작이 synchronized로 처리되기 때문에
여러 쓰레드에서 동시에 로깅을 하려고하면 대기시간이 필요해진다.
🤔 LogBack이란?
Slf4j 의 구현체로 Spring Boot 환경이라면 별도의 dependency 추가 없이 기본적으로 포함되어 있다.
Logback 은 log4j 에 비해 향상된 필터링 정책, 기능, 로그 레벨 변경 등에 대해 서버를 재시작할 필요 없이 자동 리로딩을 지원한다는 장점을 가진다.
📌 로그 레벨
Error : 예상하지 못한 심각한 문제가 발생하는 경우
Warn : 로직 상 유효성 확인, 예상 가능한 문제로 인한 예외 처리
Info : 운영에 참고할만한 사항
Debug : 개발 단계에서 디버깅 용도로 사용한다. (ex. SQL 로깅)
Trace : 모든 레벨에 대한 로깅이 추적 (개발단계에서 사용)
로그의 레벨은 5가지이며, 심각도 수준은 아래와 같다.
Error > Warn > Info > Debug > Trace
레벨은 다음과 같이 동작한다.
로그 레벨을 Info로 설정 시, Info 레벨 이상의 로그레벨(Info, Warn, Error)만 출력된다.
⚙️ 로그백 설정 방법
Springboot 환경이라면 별도의 설정없이 사용이 가능하다.
로그 관련 설정 방법은 application.yml 과 logback-spring.xml 에서 설정하는 방법이 있다.
application.yml 은 설정하는 난이도가 비교적 쉽지만,
실제 제품에 사용하기엔 한계가 있고 세부적인 설정이 불편하기 때문에 logback-spring.xml 로 관리하는 편이 더 좋다고 한다.
따라서 logback-spring.xml 로 로그를 기록해보자.
우선 resources 패키지에 logback-spring.xml 을 생성한다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- base.xml default.xml 에 존재하는 Log 메시지의 Color 설정 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<!-- 콘솔에 출력되는 로그 패턴 -->
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %clr(%5level) %cyan(%logger) - %msg%n"/>
<!-- Log파일에 기록되는 로그 패턴 -->
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n"/>
<!-- 콘솔로그 Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 파일로그 Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<!-- RollingPocliy: 로그가 길어지면 가독성이 떨어지므로 로그를 나눠서 기록하기위한 규칙 -->
<!-- 로그파일을 크기, 시간 기반으로 관리하기 위한 SizeAndTimeBasedRollingPolicy -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 로그파일명 패턴 -->
<!-- 날짜별로 기록되며 maxFileSize를 넘기면 인덱스(i)를 증가시켜 새로운 이름의 로그파일에 기록을 이어간다 -->
<fileNamePattern>./log/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 로그파일 최대사이즈 -->
<maxFileSize>100MB</maxFileSize>
<!-- 생성한 로그파일 관리 일수 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- local Profile에서의 로그 설정 -->
<springProfile name="local">
<!-- 해당 패키지의 로그는 DEBUG 레벨 부터 출력 -->
<logger name="com.chiwawa.lionheart" level="DEBUG" />
<!-- 전체적인 로그는 INFO 레벨 부터 출력 -->
<root level="INFO">
<!-- CONSOLE 로그 Appender를 로그 Appender로 등록 -->
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- dev Profile에서의 로그 설정 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
<!-- prod Profile에서의 로그 설정 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
이제부터 해당 xml파일에 대해 설명해보겠다.
💭 logback.ColorConverter
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %clr(%5level) %cyan(%logger) - %msg%n"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n"/>
clr이 뭔데 만들어주는거지?
clr로 설정된 converterClass "org.springframework.boot.logging.logback.ColorConverter"은 logback의 defaults.xml에 설정된 로그 색상 입력 컨버터이다.
defaults.xml엔 로그 패턴들이 관리되고 있는데, CONSOLE_LOG_PATTERN에 적용된 컬러가 clr로 되어있음을 알 수 있다.
원래 Log에 색상을 입히려면 %색상코드 와 같은 형태로 입력해주어야하는데, 해당 컨버터를 가져와 %clr 로 설정해주면 로그에 맞는 색상이 자동으로 추가된다.
💭 Log Pattern
LOG_PATTERN 들은 출력할 로그의 패턴을 설정해놓은 property들인데, FILE_LOG_PATTERN에는 색상을 적용하지 않는다.
색을 적용하면 색상코드로 인해 로그가 지저분해지기 때문이다.
Pattern
로그 출력하기 위해서 어떠한 포맷을 정의할 수 있습니다. 패턴에 사용되는 요소는 다음과 같다.
- %Logger{length} - Logger name을 축약할 수 있다. {length}는 최대 자리 수
- %thread - 현재 Thread 이름
- %-5level - 로그 레벨, -5는 출력의 고정폭 값
- %msg - 로그 메시지 (=%message)
- %n - new line
- ${PID:-} - 프로세스 아이디
- %d : 로그 기록시간
- %p : 로깅 레벨
- %F : 로깅이 발생한 프로그램 파일명
- %M : 로깅이 발생한 메소드의 이름
- %l : 로깅이 발생한 호출지의 정보
- %L : 로깅이 발생한 호출지의 라인 수
- %t : 쓰레드 명
- %c : 로깅이 발생한 카테고리
- %C : 로깅이 발생한 클래스 명
- %m : 로그 메시지
- %r : 애플리케이션 시작 이후부터 로깅이 발생한 시점까지의 시간
💭 Appender
로그의 형태를 설정하며, 로그 메세지를 콘솔에 출력할지, 파일에 출력할지 등의 설정을 통해 출력될 대상을 결정하는 요소로,
다시말해 로그 출력 방식 관리자 이다.
자세한 내용은 주석확인~!
<!-- 콘솔로그 Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 파일로그 Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<!-- SizeAndTimeBasedRollingPolicy: 로그파일을 크기, 날짜 기반으로 관리하기 위한 롤링 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 로그파일명 패턴 -->
<!-- 날짜별로 기록되며 maxFileSize를 넘기면 인덱스(i)를 증가시켜 새로운 이름의 로그파일에 기록을 이어간다 -->
<fileNamePattern>./log/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 로그파일 최대사이즈 -->
<maxFileSize>100MB</maxFileSize>
<!-- 생성한 로그파일 관리 일수 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
appender 는 자주 사용하는 아래 목록에 대해서만 정리하도록 하겠다.
- ConsoleAppender 는 로그를 OutputStream 에 작성하여 콘솔에 출력.
- FileAppender 는 파일에 로그를 출력하며, 최대 보관 일수, 파일 용량 등을 지정 가능.
- RollingFileAppender 는 FileAppender 를 상속 받으며, 여러 개의 파일을 롤링, 순회하면서 로그를 파일에 출력한다.
- SizeAndTimeBasedRollingPolicy에 의해 지정된 용량이 넘어간 로그 파일을 넘버링하여서 나눠서 저장이 가능.
문제 해결 포인트 🔥
위 의 주요 문제해결 포인트는 FILE Appender의 <maxFileSize>, <maxHistory> 라고 할 수 있겠다.
이전에는 모든 로그들이 하나의 파일에 전부 저장되었기 때문에 용량, 가독성 문제가 있었다.
하지만 위 설정을 통해 100MB 단위로 로그 파일을 저장하며, 30개까지만 저장하기 때문에 이전 기록은 자연스레 삭제되어 용량으로 인한 문제가 발생하지 않을 것이다. (현 EC2의 EBS는 27GiB(29GB)로 3000MB 정도는 거뜬하게 관리할 수 있는 환경이다)
또한 로그 파일의 이름이 날짜,인덱스 단위로 구분되기 때문에 필요한 시점의 로그를 쉽게 확인할 수 있을 것이다.
💭 Profile 설정
profile환경에 따른 로그 관리 설정 코드이다.
<!-- local Profile에서의 로그 설정 -->
<springProfile name="local">
<!-- 해당 패키지의 로그는 DEBUG 레벨 부터 출력 -->
<logger name="com.chiwawa.lionheart" level="DEBUG" />
<!-- 전체적인 로그는 INFO 레벨 부터 출력 -->
<root level="INFO">
<!-- CONSOLE 로그 Appender를 Appender로 등록 -->
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- dev Profile에서의 로그 설정 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
<!-- prod Profile에서의 로그 설정 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
운영서버는 저장공간과 성능 측면을 많이 봐야하기 때문에 로그를 다른 환경에서 보다 효율적으로 관리해야할 필요가 있다.
그렇기에 팀원과의 협의를 바탕으로 운영 서버만의 Appender를 만들어 다르게 관리하는 방법도 생각해보면 좋을 것 같다!
reference
https://lovethefeel.tistory.com/89
https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/
https://breakcoding.tistory.com/400
'BackEnd > Spring' 카테고리의 다른 글
SpringBoot 멀티모듈 프로젝트 - 모듈 간 의존관계 설정 오류 해결 (0) | 2024.01.10 |
---|---|
SpringBoot 서버 배포 시, 한국 시간(KTC)으로 배포하는 방법 (1) | 2023.08.02 |
스프링부트 예외 발생 시, Slack으로 알림 보내기 (feat. Slack WebHooks) (2) | 2023.06.29 |
JPA @Modifying 어노테이션 - clearAutomatically, flushAutomatically란? (0) | 2023.06.27 |
스프링부트에서 Response 시, JSON의 한글이 깨지는 오류 해결 (0) | 2023.03.22 |