본문 바로가기
Framework/Spring Boot

[Spring Boot] Logback 완벽 가이드: 서버의 유일한 목격자, 로깅(Logging) 마스터하기

반응형

초보 개발자와 실무 개발자를 구분하는 가장 확실한 기준 하나를 꼽으라면 바로 '로깅(Logging)을 어떻게 다루는가?'입니다. 대학생이나 학원 시절 프로젝트에서는 에러가 나거나 변수 값을 확인하고 싶을 때 무조건 System.out.println("여기로 들어옴!!")을 난사했습니다.
하지만 실제 운영(Production) 서버에서 System.out.println을 쓰면, 치명적인 성능 저하(블로킹 I/O)가 발생할 뿐만 아니라, 서버가 재시작되는 순간 콘솔에 찍힌 글자들이 흔적도 없이 증발해 버립니다. 어젯밤 11시에 결제가 실패해서 고객센터로 항의가 들어왔는데, 원인을 찾으려고 보니 로그 기록이 완전히 날아가 있다면 개발자는 등골이 오싹해질 수밖에 없습니다.

이번 [Spring Boot 실무 완벽 가이드] 시리즈에서는 "과거의 장애를 역추적하는 서버의 블랙박스!" Spring Boot의 기본 로깅 프레임워크인 Logback(로그백)의 완벽한 통제와 파일 로테이션(Rotation) 실무 기법에 대하여 다뤄보겠습니다.

🚫 1. 왜 System.out.println을 쓰면 해고당할까?

자바의 System.out.println()은 매우 무거운 동기(Synchronous) 작업입니다. 한 쓰레드가 화면에 글자를 다 찍을 때까지 다른 쓰레드들은 줄을 서서 기다려야 합니다. 또한, 이 텍스트들을 레벨(Level)별로 분류할 수도 없고, 파일로 저장할 수도 없습니다.

반면 전문 로깅 프레임워크(Slf4j + Logback)를 사용하면 TRACE < DEBUG < INFO < WARN < ERROR 라는 5단계의 로깅 레벨을 부여할 수 있습니다. "개발 서버에서는 아주 사소한 변수 값(DEBUG)까지 다 출력하고, 상용 서버에서는 성능을 위해 중요한 에러(ERROR)나 알림(INFO)만 출력해라!"라는 섬세한 튜닝이 yml 파일 수정 한 줄로 가능해집니다.


📝 2. Slf4j와 Lombok @Slf4j의 우아한 활용

스프링 부트(Spring Boot)는 내부에 Logback이라는 빠르고 강력한 로깅 엔진을 기본 탑재하고 있습니다. 개발자는 이 엔진을 직접 호출하는 대신, Slf4j(Simple Logging Facade for Java)라는 공통 인터페이스를 통해 로그를 남깁니다. 롬복(Lombok)을 쓰고 있다면 클래스 위에 @Slf4j 어노테이션 하나만 붙이면 log 객체가 마법처럼 생성됩니다.

@Slf4j // 🌟 Lombok이 자동으로 private static final Logger log = LoggerFactory.getLogger(...) 코드를 만들어줍니다!
@Service
public class OrderService {
    
    public void processOrder(Long orderId) {
        log.info("주문 처리 시작. Order ID: {}", orderId); // {} 안에 변수가 쏙 들어갑니다 (성능 최적화)
        
        try {
            // 결제 로직...
            log.debug("세부 결제 데이터 검증 중..."); // 개발 서버에서만 보고 싶은 상세 로그
        } catch (Exception e) {
            log.error("주문 처리 중 치명적인 에러 발생! Order ID: {}", orderId, e); // 에러 로그와 Exception 객체를 함께 넘김
        }
    }
}

⚙️ 3. logback-spring.xml: 파일 기반 영구 저장과 로테이션(Rotation)

콘솔에 로그를 예쁘게 찍는 것까지는 성공했습니다. 이제 진짜 실무의 영역인 '로그 파일 남기기' 차례입니다. 로그를 한 파일(예: error.log)에 1년 내내 누적해서 쓴다면 나중에는 파일 용량이 100GB가 넘어가서 메모장으로 열 수도 없게 됩니다.

실무에서는 "매일 자정(밤 12시)이 되면 어제 썼던 로그 파일을 날짜를 붙여서(예: error-2024-03-15.log) 압축(zip) 보관하고, 30일이 지난 아주 오래된 로그는 서버 용량을 위해 자동으로 싹 지워라!"라는 마법의 룰을 적용합니다. 이를 RollingFileAppender (롤링 파일 어펜더)라고 부릅니다. src/main/resources 아래에 logback-spring.xml 파일을 만들어 이 완벽한 룰을 지시합니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 로그 파일이 저장될 폴더 위치 -->
    <property name="LOG_PATH" value="/var/log/my-awesome-app"/>

    <!-- 1. 날짜별로 쪼개서(Rolling) 저장하는 로직 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 매일 자정마다 새로운 파일로 분리하고, 지난 파일은 날짜를 붙여 압축(.gz) -->
            <fileNamePattern>${LOG_PATH}/application-%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <!-- 과거 로그는 최대 30일(한 달) 치만 보관하고 나머지는 자동 삭제! (디스크 터짐 방지) -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <!-- 로그에 찍힐 포맷: [날짜/시간] [쓰레드명] [로그레벨] [클래스명] - 로그내용 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss}] [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 상용(PROD) 프로필로 실행될 때만 파일로 저장해라! -->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE" />
        </root>
    </springProfile>
</configuration>

🎯 4. 마무리 및 다음 단계

지금까지 서버가 무너지고 에러가 터져도 마지막까지 남아 진실을 알려주는 유일한 단서이자 블랙박스, Logback 시스템을 통한 체계적인 로그 레벨링과 파일 로테이션(Rolling) 최적화 기법에 대해 다루어 보았습니다. 이제 여러분의 서버는 과거의 장애를 완벽하게 추적할 수 있는 튼튼한 체계를 갖추었습니다.

로그도 잘 남겼으니, 이제 코드를 본격적으로 짜볼 시간입니다. 그런데 앞선 03단계(MVC 패턴) 코드 예제에서 컨트롤러 위에 @RequiredArgsConstructor를 붙여놓고 주방장(Service) 객체를 그냥 선언만 했는데, 도대체 누가 저기에 진짜 서비스 객체를 생성(new)해서 넣어준 걸까요? 우리는 new UserService()라고 적은 적이 한 번도 없습니다! 이어지는 06단계 포스팅에서는 "객체의 생성과 죽음을 내가 아닌 스프링이 대신 통제한다!" 스프링 프레임워크의 존재 이유이자 가장 거대한 마법, '의존성 주입(DI)과 IoC 컨테이너의 완벽한 해부'에 대해 아주 뼈 때리게 파헤쳐 보겠습니다!

반응형