6-1. 스프링 MVC 로깅

shin·2025년 8월 3일

Spring MVC

목록 보기
23/25
  • 스프링 프로젝트 작성 시 디버깅을 위해 System.out.println()을 사용함
  • 하지만 운영 환경에서는 반드시 로깅 라이브러리를 통해 로그를 남겨야 함

로그를 써야하는 이유


  • 로그 레벨로 출력 범위 조절 가능 (예 : 운영 환경에서는 info만, 개발 환경은 debug 까지)

  • 출력 포맷, 시간, 쓰레드명, 클래스명 등 부가 정보 자동 포함

  • 콘솔 외에도 파일, 네트워크, 원격 서버 등에 로그 저장 가능

  • 성능도 더 좋음(버퍼링, 비동기 등)

  • 로그 분할(일자/용량 기준 분할 저장 등) 가능



스프링에서 사용하는 로깅 시스템


스프링 부트의 기본 설정

  • 스프링 부트 스타터는 기본적으로 아래 두 가지를 포함함
    • SLF4J(Simple Logging Facade for Java) : 인터페이스
    • Logback : 실제 구현체(기본 설정)

SLF4J는 여러 로깅 라이브러리(Log4j, Logback 등)를 추상화해서 통합해주는 역할


로그 사용 예시


컨트롤러 클래스

package hello.springmvc.basic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogTestController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest() {
    
        String name = "Spring";
        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        log.info(" info log={}", name);
        log.warn(" warn log={}", name);
        log.error("error log={}", name);

        // 잘못된 사용법: 로그 출력 여부와 관계없이 문자열 더하기 연산 발생
        log.debug("String concat log=" + name);

        return "ok";
    }
}
  • @RestController는 반환 값을 HTTP Response Body로 직접 전달함
  • 뷰를 렌더링하지 않기 때문에 "ok" 문자열이 바로 출력됨

로그 레벨 종류


TRACE > DEBUG > INFO > WARN > ERROR
  • 개발 환경 : DEBUG까지 출력
  • 운영 환경 : INFO부터 출력

로그 설정(application.properties)


# 전체 로그 레벨 (기본: info)
logging.level.root=info

# 특정 패키지 로그 레벨 설정
logging.level.hello.springmvc=debug

올바른 로그 사용법


잘못된 사용 예

log.debug("data=" + data);
  • 로그가 출력되지 않아도 문자열 연산은 항상 실행됨

권장되는 방식

log.debug("data={}", data);
  • 로그 레벨 조건을 만족하지 않으면 연산 자체도 실행되지 않음

@Slf4j를 사용한 간편 선언(Lombok)


@Slf4j
public class LogTestController {

    @RequestMapping("/log-test")
    public String logTest() {
    
        log.info("hello");
        return "ok";
    }
}
  • LoggerFactory.getLogger()를 직접 작성할 필요 없이 간편하게 사용 가능
  • 단, lombok 의존성이 필요함


Logback 설정 확장하기


  • 스프링 부트는 기본적으로 application.propertiesapplication.yml을 통해 간단한 설정이 가능하지만,
  • 보다 세부적인 설정을 원할 경우에는 logback-spring.xml 파일을 사용해 커스터마이징할 수 있음

logback-spring.xml 위치

  • src/main/resource 폴더 안에 생성
  • 이름은 꼭 logback-spring.xml로 해야 Spring Boot에서 프로파일을 인식함 !

예시 : 로그 파일 저장 + 콘솔 출력

  • Logback이라는 로깅 시스템의 XML 설정 파일
  • 로그를 콘솔과 파일로 동시에 출력하고, 파일은 일별로 저장되며 30일치만 유지하도록 설정
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 콘솔 출력 -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 파일 출력 (일별로 생성) -->
    <appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory> <!-- 최근 30일치 보관 -->
        </rollingPolicy>

        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 전체 로그 레벨 설정 -->
    <root level="INFO">
        <appender-ref ref="Console" />
        <appender-ref ref="File" />
    </root>

</configuration>

Appender : 로그를 터미널/IDE 콘솔, 파일에 출력

<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
  • Console이라는 이름의 로그 출력기(appender) 정의
    • 로그를 터미널/콘솔에 출력하도록 설정
<appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
  • File이라는 이름의 파일 출력용 Appender
  • 로그를 logs/app.log 파일에 기록함
    • <file>logs/app.log</file> : 기본 로그 파일 경로

로그 메시지에 포함될 포맷 지정

 <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
  • encoder - pattern : 로그 메시지를 출력할 형식을 지정하는 부분
    • 패턴 설명
      • %d{yyyy-MM-dd HH:mm:ss} → 로그 발생 시간
      • [%thread] → 실행 중인 쓰레드 이름
        - %-5level → 로그 레벨 (INFO, ERROR 등)
      • %logger{36} → 로거 이름(보통 클래스 이름)
      • %msg → 실제 로그 메시지
      • %n → 줄 바꿈

RollingFileAppender

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
    <maxHistory>30</maxHistory>
</rollingPolicy>
  • Rolling 정책 설정 : 날짜별로 로그 파일을 자동 분리 저장
    • logs/app.2025-08-03.log 이런 식으로 생성됨
    • maxHistory=30이면 최근 30일치만 보관하고, 이전 것은 자동 삭제됨

<encoder>
    <pattern>...</pattern>
</encoder>
  • 콘솔과 동일한 포맷으로 파일에도 로그를 기록

전체 로그 레벨 설정

<root level="INFO">
    <appender-ref ref="Console" />
    <appender-ref ref="File" />
</root>
  • 루트 로거 설정
    • 전체 프로젝트의 기본 로그 레벨은 INFO
    • Console과 File 두 군데에 로그를 동시에 출력함
    • 루트 레벨은 전체 로그 출력 기준이 되며, INFO보다 낮은 TRACE, DEBUG 로그는 기본적으로 출력되지 않음


비동기 로그 처리


  • 동기 방식의 로그는 성능에 영향을 줄 수 있음
  • 실무에서는 비동기 로그 처리를 통해 성능을 개선할 수 있음

기존 방식(동기)

[비즈니스 로직 수행] → [로그 출력] → [다음 작업]
  • 로그를 출력하는 동안 파일 쓰기(I/O)가 끝날때까지 기다림
  • 특히 파일에 로그 남길 때, 속도는 느려질 수 있음

비동기 방식

[비즈니스 로직 수행] → [로그 큐에 넣음] → [다음 작업]
                               ↘ [백그라운드에서 기록]
  • 로그는 큐에 쌓고 바로 다음 로직 수행
  • 별도 쓰레드에서 로그를 천천히 파일로 씀

예시(logback-spring.xml)

<appender name="AsyncFile" class="ch.qos.logback.classic.AsyncAppender">
    <!-- 비동기 처리할 실제 대상 Appender -->
    <appender-ref ref="File"/>
    
    <!-- 큐가 꽉 찼을 때 어떻게 할지 설정 (기본 false면 로그 손실 가능) -->
    <discardingThreshold>0</discardingThreshold>

    <!-- 로그 큐 최대 크기 (기본: 256) -->
    <queueSize>512</queueSize>

    <!-- 예외 발생 시 즉시 출력할지 여부 -->
    <includeCallerData>true</includeCallerData>
</appender>
  • 이렇게 하면 로그를 별도 쓰레드에서 비동기로 처리하게 됨
  • 파일 I/O로 인한 병목을 줄일 수 있어서 트래픽 많은 서비스에서는 필수적으로 사용함

주요 설정 설명

속성설명
appender-ref실제 로그 출력을 담당할 Appender를 지정 (File, Console 등)
discardingThreshold로그 큐가 꽉 찼을 때 버릴 로그 레벨 기준 (0이면 전부 보존 시도)
queueSize로그를 임시 저장할 큐 크기 (기본 256개, 많을수록 메모리 사용 증가)
includeCallerData로그 발생한 클래스/메서드 정보까지 포함할지 여부 (true 추천)

전체 예시 구조

<configuration>

    <!-- 실제 파일 로그 Appender -->
    <appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 비동기 로그 Appender (File을 감싸서 비동기 처리) -->
    <appender name="AsyncFile" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="File"/>
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
        <includeCallerData>true</includeCallerData>
    </appender>

    <!-- 루트 로그 설정: AsyncFile 사용 -->
    <root level="INFO">
        <appender-ref ref="Console" />
        <appender-ref ref="AsyncFile" />
    </root>

</configuration>

실무 적용 시

상황설정 방법
트래픽이 많고 I/O 병목이 있을 때AsyncAppender로 감싸기
로그 손실이 나면 안 될 때discardingThreshold=0, queueSize 늘리기
로그가 너무 많아서 메모리 부담될 때큐 크기 조절 또는 로그 레벨 조정


정리


항목권장 방식
로그 출력 방식log.info("data={}", data)
로그 선언 방식@Slf4j (Lombok 활용)
로그 저장logback-spring.xml로 파일 저장 설정
비동기 처리AsyncAppender 사용
운영 vs 개발운영: info 이상, 개발: debug 이상
profile
Backend development

0개의 댓글