현재 학점가방이라는 앱 서비스를 제작 중이며 나는 스프링 부트로 백엔드 개발을 맡고 있다. 지금까지 프론트엔드 개발자들이 API 연결과정에서 로그를 확인해야 할 때 직접 서버에 접속하여 docker logs 명령을 통해 콘솔로만 확인해왔다. 하지만 이 방식은 다음과 같은 문제점이 있었다.
이러한 이유들로 우리 팀은 좀 더 효율적인 방식으로 로그를 관리하기로 결정하였다.
로그(log)란 프로그램 또는 시스템에서 발생하는 이벤트, 정보, 상태, 오류 등을 기록한 것을 말하고, 이러한 로그를 생성하고 저장하는 것을 로깅(logging)이라 한다.
로그는 프로그램의 동작을 추적해 문제를 해결하거나 성능을 분석하기 위한 목적으로 활용된다. 이 때문에 로그 관리는 프로그램 개발과 운영에서 아주 중요한 역할을 한다.
로그를 관리하기 위한 여러 도구들을 찾아보았고 Logback, java.tuil.logging, Log4j2와 같은 같은 선택지가 있었다. 그리고 챗지피티를 활용하여 이들의 장단점을 비교해보았다.

나는 이 중에서는 스프링 부트에서 기본적으로 제공하고 있으며 권장하고 있는 Logback을 사용하기로 결정하였다.
Logback은 Java에서 가장 널리 사용되는 로깅 프레임워크 중 하나로, Log4j의 창시자인 Ceki Gülcü가 개발했다. 또한 오랫동안 검증된 Log4j의 아키텍쳐 기반으로 작성되었다.
SLF4J와 함께 동작한다.로그라고 해서 모두 다 같은 로그는 아니다. API 요청을 했다는 정보성 로그가 있고, 예상치 못한 오류로 인해 발생한 로그도 있을 것이다. Logback에서는 이러한 로그들을 5단계로 분류하여 출력한다.
Logback에서는 다음과 같은 방식으로 로그 레벨을 분류한다.
Error : 애플리케이션 오류(예외 발생, 장애 감지)
Warn : 예외는 아니지만 문제 가능성이 있는 경우(오래 걸리는 요청, 메모리 부족 경고)
Info : 정상적인 시스템 동작 로그(API 요청, 중요한 비즈니스 이벤트)
Debug : 개발용 상세 로그(서비스 내부 로직)
Trace : 상세한 디버깅용(DB 쿼리 상세 로그)
심각도는 오른쪽으로 갈수록 높아진다.
Trace > Debug > Info > Warn > Error
모든 로그를 출력하면 로그 파일의 용량이 빠르게 증가하고 서버의 저장공간 부족문제가 발생할 수도 있다. 그래서 일반적으로 특정 레벨 이상의 로그만 출력하도록 설정한다.
예를 들어 로그 레벨을 Info로 설정 시, Info 레벨 이상의 로그레벨(Info, Warn, Error)이 출력된다.
스프링 부트에서는 logback-spring.xml 혹은 application.properties 을 사용하여 로그를 관리할 수 있다. 나는 이 중에서 profile에 따라 다양한 출력 방식을 지정할 수 있는 logback-spring.xml 방식을 사용하기로 결정했다.
먼저 resoures폴더 아래에 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"/>
<!-- 파일 로그 패턴 -->
<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">
<!-- 전체적인 로그는 INFO 레벨 부터 출력 -->
<root level="INFO">
<!-- CONSOLE 로그 Appender를 로그 Appender로 등록 -->
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
<!-- dev Profile에서의 로그 설정 -->
<springProfile name="dev">
<root level="INFO">
<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>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
%clr(...) 패턴을 사용할 때 색상을 지정할 수 있도록 해준다.<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"/>
${CONSOLE_LOG_PATTERN}
%d{yyyy-MM-dd HH:mm:ss.SSS} → 로그의 날짜 및 시간 (밀리초 단위 포함)[%thread] → 해당 로그를 출력한 스레드명%clr(%5level) → 로그 레벨 (INFO, DEBUG, ERROR 등), 색상을 적용%cyan(%logger) → 로그를 출력한 클래스명을 파란색으로 표시%msg%n → 로그 메시지를 출력하고 개행${FILE_LOG_PATTERN}
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
stdout)에 로그를 출력하는 ConsoleAppender를 설정한다.${CONSOLE_LOG_PATTERN}을 사용한다.<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>
${FILE_LOG_PATTERN}을 사용한다.fileNamePattern./log/%d{yyyy-MM-dd}.%i.logmaxFileSize를 초과하면 인덱스(%i)를 증가시켜 새 파일을 만든다.maxFileSize100MB를 초과하면 새 파일을 생성한다.maxHistory<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</springProfile>
INFO 이상의 레벨에 대한 로그를 출력한다.스프링부트 앱 메인함수를 다음과 같이 작성하고 실행해보았다.
@SpringBootApplication
public class UniBagSpringBootAppApplication {
private static final Logger logger = LoggerFactory.getLogger(UniBagSpringBootAppApplication.class);
public static void main(String[] args) {
SpringApplication.run(UniBagSpringBootAppApplication.class, args);
logger.info("애플리케이션이 시작되었습니다.");
logger.debug("디버그 로그입니다.");
logger.error("에러 로그입니다.");
}
}

로컬 환경에서 실행을 하였기 때문에 INFO 이상의 레벨에 해당하는 로그들이 잘 출력되는 것을 알 수 있다.