본문으로 건너뛰기
Advertisement

2.10 로깅 전략 (SLF4J / Logback)

스프링 부트는 SLF4J를 퍼사드로, 구현체로 Logback을 기본 사용합니다. 운영에서는 로그 레벨·형식·요청 추적(MDC) 을 정리해 두는 것이 중요합니다.

작성 기준: Spring Boot 3.2.x (spring-boot-starter-logging → Logback 포함)


1. SLF4J와 Logback 관계

  • SLF4J (Simple Logging Facade for Java): 로그 API(퍼사드). 코드에서는 Logger를 통해 로그를 남깁니다.
  • Logback: SLF4J의 구현체. 스프링 부트는 spring-boot-starter-logging에 Logback을 포함하므로 별도 의존성 추가 없이 사용합니다.
  • logback-classic: SLF4J 바인딩 + Logback 코어. logback-core: 실제 Appender·Encoder 등 구현.

애플리케이션 코드는 SLF4J만 사용하고, 어디에·어떤 형식으로 출력할지는 Logback 설정으로 제어합니다.


2. 기본 사용 (SLF4J)

@Slf4j  // Lombok
@Service
public class OrderService {

public void create(OrderRequest req) {
log.info("주문 생성 요청: orderId={}", req.getOrderId());
// ...
log.debug("저장 완료: {}", order);
}
}
  • 플레이스홀더로 문자열 결합을 피하면, 레벨이 꺼져 있을 때 불필요한 연산을 하지 않습니다.

3. application.yml로 레벨 설정

logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG # SQL 로그 (개발 시)
  • 패키지별 레벨 지정 가능. 운영에서는 root: INFO, 필요 패키지만 DEBUG로 두는 경우가 많습니다.

4. MDC — 요청별 추적

한 요청을 요청 ID로 묶어 추적하려면 MDC(Mapped Diagnostic Context) 를 사용합니다.

@Component
public class RequestIdFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, ...) {
String requestId = request.getHeader("X-Request-Id");
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
MDC.put("requestId", requestId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}

logback-spring.xml에서 패턴에 %X{requestId}를 넣으면 로그에 요청 ID가 포함됩니다.


5. Logback 설정 파일 (logback-spring.xml)

설정 파일은 src/main/resources/logback-spring.xml (또는 logback.xml)에 둡니다. logback-spring.xml을 쓰면 스프링 부트가 확장 기능(springProfile, springProperty)을 제공합니다.

5.1 configuration 구조

  • <appender>: 로그를 어디로 보낼지 정의 (콘솔, 파일, 소켓 등).
  • <logger>: 특정 패키지/클래스의 레벨과 사용할 appender.
  • <root>: 최상위 로거. 모든 로그의 기본 레벨과 appender.

5.2 기본 콘솔 + 패턴 예시

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %X{requestId} %-5level %logger{36} - %msg%n"/>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

자주 쓰는 패턴 변수

변수의미
%d타임스탬프
%thread스레드 이름
%X{key}MDC 값
%-5level로그 레벨 (5자 맞춤)
%logger{36}로거 이름 (최대 36자)
%msg메시지
%n줄바꿈
%ex예외 스택 트레이스

5.3 로거 상속 관계 (Logger Hierarchy & Additivity)

Logback의 로거(Logger)이름 기준 계층으로 상속됩니다. 로거 이름은 보통 패키지·클래스 경로와 같습니다 (예: com.example.service.OrderService).

계층 구조

  • root (이름 없음): 최상위. 모든 로거의 부모.
  • com: root의 자식
  • com.example: com의 자식
  • com.example.service: com.example의 자식
  • com.example.service.OrderService: com.example.service의 자식

레벨 상속: 자식 로거에 level을 지정하지 않으면 부모의 레벨을 따릅니다. 부모도 없으면 root까지 올라가서 root의 레벨이 적용됩니다.

<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>

<!-- com.example 아래는 모두 INFO 상속. 단, com.example.service만 DEBUG로 오버라이드 -->
<logger name="com.example.service" level="DEBUG"/>
  • com.example.service.OrderService에서 남긴 로그 → DEBUG (com.example.service 로거 설정 사용)
  • com.example.controller에서 남긴 로그 → INFO (root 상속)

additivity (Appender 상속)

기본값은 additivity="true" 입니다. 자식 로거에 appender를 붙이면, 그 로그는 자식 로거의 appender에 출력되고, 부모·조상 로거의 appender에도 그대로 전달됩니다. 그래서 같은 로그가 root의 CONSOLE에도 찍혀 중복 출력이 날 수 있습니다.

  • additivity="false" 로 두면, 해당 로거에서 처리한 로그는 부모로 전달되지 않습니다. 특정 패키지만 별도 파일로 빼고 root 콘솔에는 안 나오게 할 때 사용합니다.
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="SERVICE_FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
  • com.example.service 패키지 로그는 SERVICE_FILE로만 가고, CONSOLE에는 가지 않습니다.
  • com.example.controller 로그는 additivity가 true(기본)이므로 root만 따르고 CONSOLE로만 갑니다.

정리: 레벨은 가장 가까운 조상에서 결정되고, appender는 additivity가 true일 때 자신 + 모든 조상 appender에 전달됩니다. 중복을 막으려면 additivity="false" 를 고려하세요.

5.4 로깅 설정 옵션 참조

실무에서 자주 쓰는 Logback·application.yml 옵션을 정리했습니다.

로그 레벨 (Level)

레벨의미사용 시점
TRACE가장 상세흐름 추적, 디버깅 초기 단계 (운영에서는 거의 사용 안 함)
DEBUG디버그 정보개발 시 SQL·파라미터·내부 상태 확인
INFO일반 정보요청·처리 완료·주요 비즈니스 이벤트 (운영 기본)
WARN경고복구 가능한 이상, deprecated 사용 등
ERROR오류예외·실패·복구 불가 상황

레벨은 해당 레벨 이상만 출력됩니다. level="INFO"이면 INFO, WARN, ERROR만 남고 DEBUG·TRACE는 제외됩니다.

ConsoleAppender 옵션

옵션설명예시
encoder출력 형식 정의. 하위에 pattern, charset 사용위 5.2 예시 참고
pattern변환 패턴 문자열. %d, %level, %msg 등 조합%d{yyyy-MM-dd HH:mm:ss} %-5level %msg%n
charset인코딩UTF-8
target출력 대상. System.out(기본) / System.errSystem.err

RollingFileAppender 옵션

옵션설명예시
file현재 쓰는 로그 파일 경로${LOG_PATH}/app.log
encoder파일에 쓸 형식 (pattern, charset)동일
rollingPolicy로테이션 정책 (아래 참고)SizeAndTimeBasedRollingPolicy
append기존 파일에 이어쓸지 (기본 true)true

SizeAndTimeBasedRollingPolicy 옵션

옵션설명예시
fileNamePattern롤링된 파일 이름. %d{...}(날짜), %i(인덱스) 사용app-%d{yyyy-MM-dd}.%i.log.gz
maxFileSize파일당 최대 크기. 초과 시 새 파일로 롤링100MB, 500MB
maxHistory보관할 롤링 파일 개수(일 단위일 때는 일수)30 (30일)
totalSizeCap모든 롤링 파일 합계 상한. 초과 시 오래된 파일부터 삭제3GB
cleanHistoryOnStart기동 시 오래된 파일 정리 (기본 false)true

TimeBasedRollingPolicy (일자만 기준)

일자만으로 롤링할 때 사용합니다. fileNamePattern%d{yyyy-MM-dd} 등만 넣고, maxHistory로 보관 일수를 제한합니다. maxFileSize는 없습니다.

Encoder (PatternLayoutEncoder) 옵션

옵션설명예시
pattern변환 패턴%d %level [%thread] %logger - %msg%n
charset문자 인코딩UTF-8
immediateFlush매 로그마다 플러시 (기본 true). false면 버퍼링true

패턴 변수 추가 참고

변수설명
%d{format}날짜. format 예: yyyy-MM-dd HH:mm:ss.SSS, ISO8601
%relative기동 후 경과 밀리초
%level / %-5level레벨 (숫자는 최소 너비, -는 왼쪽 정렬)
%logger{length}로거 이름. %logger{36}이면 FQCN 36자로 축약
%class / %method / %line호출 클래스·메서드·라인 (비용 있음)
%ex / %throwable예외 스택. %ex{short}면 한 줄 요약
%X{key}MDC 값. %X만 쓰면 전체 MDC

AsyncAppender 옵션

옵션설명기본값비고
queueSize대기 큐 크기256넘치면 discardingThreshold 적용
discardingThreshold큐 잔량이 이 비율(%) 미만이면 TRACE/DEBUG/INFO 버림. 0이면 레벨 구분 없이 버림200이면 모두 버릴 수 있음
includeCallerData호출 위치(클래스·라인) 포함 여부. 비동기에서는 비용 큼false필요 시만 true
neverBlock큐 가득 찼을 때 블로킹 대신 로그 드롭falsetrue면 드롭, false면 블로킹
appender-ref실제로 쓰는 하위 appender1개만 지정

application.yml — logging 옵션

설명예시
logging.level.rootroot 로거 레벨INFO
logging.level.패키지패키지별 레벨com.example: DEBUG
logging.file.name로그 파일 경로 (이름까지)logs/app.log
logging.file.path로그 디렉터리 (파일명은 spring.log)logs
logging.pattern.console콘솔 출력 패턴%d %-5level %logger{36} - %msg%n
logging.pattern.file파일 출력 패턴동일
logging.pattern.dateformat%d 기본 날짜 형식yyyy-MM-dd HH:mm:ss.SSS

logback-spring.xml이 있으면 logging.pattern·logging.file 등은 XML 설정이 우선합니다. 레벨만 yml로 두고 패턴·appender는 XML에서 관리하는 조합이 많습니다.


6. 실전: RollingFileAppender (파일 로테이션)

운영에서는 파일로 남기고, 용량·일자 기준으로 로테이션하는 경우가 많습니다.

<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE" value="application"/>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
  • SizeAndTimeBasedRollingPolicy: 일자 + 크기 기준. 같은 날 100MB 넘으면 %i로 새 파일.
  • maxHistory: 보관할 일수 (30일).
  • totalSizeCap: 전체 로그 용량 상한.

7. 프로파일별 설정 (springProfile)

개발/운영에서 다른 appender·레벨을 쓰려면 springProfile로 분기합니다.

<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>

<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example" level="INFO"/>
</springProfile>

8. springProperty — application.yml 값 참조

설정값을 application.yml에서 가져와 로그 경로·파일명에 쓸 수 있습니다.

<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="logs"/>
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="app"/>
  • source: application.yml의 키. defaultValue: 없을 때 사용할 값.

9. AsyncAppender — 비동기 출력

로그 I/O가 많을 때 AsyncAppender로 버퍼에 쌓았다가 비동기로 전달하면 요청 스레드 지연을 줄일 수 있습니다.

<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>

<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
  • queueSize: 대기 큐 크기. 넘치면 discardingThreshold 비율만큼 TRACE/DEBUG부터 버립니다 (0이면 다 버림).
  • appender-ref: 실제로 로그를 쓰는 하위 appender (FILE 등).

10. JSON 로그 (실전 수집·검색)

ELK, CloudWatch, Datadog 등으로 수집·검색할 때는 JSON 한 줄(one JSON per line)이 유리합니다. logstash-logback-encoder를 쓰면 패턴 대신 JSON으로 출력할 수 있습니다.

implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app-json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-json-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>requestId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
  • LogstashEncoder: 타임스탬프·레벨·로거·메시지·예외·MDC를 JSON 필드로 출력합니다.
  • includeMdcKeyName: MDC 키를 JSON 필드로 포함. 요청 ID·사용자 ID 등을 검색에 활용할 수 있습니다.

11. MDC 고급 활용

  • 요청 ID: 필터/인터셉터에서 MDC.put("requestId", id)finally에서 MDC.clear().
  • 사용자 ID: 인증 후 MDC.put("userId", userId).
  • 트랜잭션 ID: 필요 시 서비스 계층에서 설정.

자식 스레드(@Async, Executor)에서는 MDC가 자동 상속되지 않으므로, TaskDecorator로 부모 MDC를 복사해 넘기는 패턴을 사용합니다.


12. 운영 시 주의사항

  • 비밀번호·토큰·카드 번호 등은 로그에 남기지 않고, 필요 시 마스킹합니다.
  • 스택 트레이스는 ERROR 위주로 남기고, 로그 양이 많으면 파일 로테이션보존 기간(maxHistory, totalSizeCap)을 설정합니다.
  • JSON 포맷으로 출력하면 ELK·CloudWatch 등으로 수집·검색하기 쉽습니다.
  • AsyncAppender 사용 시 큐가 가득 차면 로그가 버려질 수 있으므로, queueSizediscardingThreshold를 부하에 맞게 조정합니다.

개발 시에는 SQL·파라미터를 DEBUG로 보다가, 운영에서는 root: INFO로 두고 예외·핵심 비즈니스만 남기는 구분을 권장합니다. Logback은 logback-spring.xml 한 파일에서 콘솔/파일/JSON/프로파일을 모두 제어할 수 있습니다.

Advertisement