자바 백엔드 입문 36편. log.info 한 줄 뒷단에 SLF4J·Logback이 어떻게 동작하고 로그 레벨·파일 분리·MDC 트레이스 ID까지 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 36편이에요. 시리즈 거의 모든 글에 log.info(...) 가 박혔는데 정작 로깅 자체는 안 다뤘어요. 이번 36편이 그 보강.
로깅이 헷갈리는 이유
자바 진영의 로깅 도구가 너무 많아요 — System.out.println·java.util.logging·Log4j·Log4j2·SLF4J·Logback. 무엇이 무엇의 후속이고, 어느 걸 쓸지 안 잡혀요.
이 글에서는 콘서트 사운드 시스템 비유로 풀어요. SLF4J = "표준 마이크 잭", Logback = "실제 스피커 시스템". 우리 코드는 마이크 잭 하나만 알면 됩니다.
자바 로깅의 표준 — SLF4J + Logback
SLF4J(Simple Logging Facade for Java) = "로깅 라이브러리 표준 인터페이스". 우리 코드는 SLF4J만 쓰고, 실제 구현체는 갈아끼움.
Logback = SLF4J의 가장 대표적 구현체. Spring Boot 기본 포함. 별도 의존성 X.
[우리 코드]
↓ log.info("...") ← SLF4J API
[SLF4J]
↓ 위임
[Logback] ← 실제로 콘솔·파일에 출력
다른 구현체 = Log4j2·java.util.logging. Spring Boot의 기본이 Logback이라 — 별도 설정 안 해도 바로 동작.
log.info 한 줄 사용법
Lombok @Slf4j 가 가장 흔한 패턴.
@Service
@Slf4j // ← log 변수 자동 생성
@RequiredArgsConstructor
public class OrderService {
public Order placeOrder(OrderRequest req) {
log.info("주문 생성 시작 — userId={}", req.getUserId());
try {
Order order = ...;
log.info("주문 생성 완료 — id={}, amount={}", order.getId(), order.getAmount());
return order;
} catch (Exception e) {
log.error("주문 생성 실패", e); // 마지막 인자가 Throwable이면 스택 자동
throw e;
}
}
}
@Slf4j Lombok이 — 자동으로 private static final Logger log = LoggerFactory.getLogger(OrderService.class) 코드를 생성. 우리는 log.info(...) 만 박으면 끝.
로그 레벨 5단계
log.X() 의 X는 5단계.
| 레벨 | 의미 | 사용 시점 |
|---|---|---|
| ERROR | 시스템 오류·예외 | 예외 발생·복구 불가 |
| WARN | 경고 — 문제 가능성 | 잠재적 이슈 |
| INFO | 일반 정보 — 정상 흐름 | 주요 비즈니스 이벤트 |
| DEBUG | 디버그 — 개발 환경 | 변수값·상세 흐름 |
| TRACE | 가장 상세 — 거의 안 씀 | 깊은 추적 |
계층 관계 — 운영 환경에 INFO 설정하면 — INFO·WARN·ERROR만 출력, DEBUG·TRACE는 숨김. "INFO 이상만 보여줘" 흐름.
출력 파라미터 — {} placeholder
문자열 연결 "..." + var 대신 {} placeholder 사용.
// ❌ 비효율 (DEBUG 비활성화돼도 문자열 연결 발생)
log.debug("사용자 " + user.getId() + " 처리 중");
// ✓ 효율 (DEBUG 비활성화면 연결 자체가 안 일어남)
log.debug("사용자 {} 처리 중", user.getId());
운영 환경에서 DEBUG 끄면 — 두 번째 방식은 문자열 생성 비용 0. 성능 차이.
application.yml — 로그 레벨 설정
logging:
level:
root: INFO # 전체 기본
com.example.myshop: DEBUG # 우리 패키지만 DEBUG
org.springframework.web: DEBUG # Spring 웹 로그
org.hibernate.SQL: DEBUG # JPA SQL 보기
org.hibernate.orm.jdbc.bind: TRACE # JPA 바인딩 파라미터까지
패키지 단위로 로그 레벨 분기 가능. 개발 환경에서 "우리 코드는 DEBUG, 라이브러리는 INFO" 식 자주 쓰여요.
로그 파일로 출력
기본은 콘솔만. 파일도 같이 출력하려면.
logging:
file:
name: logs/myshop.log # 단일 파일
# 또는
path: logs # 디렉토리 (spring.log 생성)
logback:
rollingpolicy:
max-file-size: 10MB # 한 파일 최대
max-history: 30 # 보관 일수
total-size-cap: 1GB
날짜별로 자동 분리 + 오래된 파일 자동 삭제 (롤링). 운영 환경 표준.
logback-spring.xml — 고급 설정
application.yml 이상의 세밀한 설정은 src/main/resources/logback-spring.xml.
<?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/myshop.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/myshop.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d %level [%X{traceId}] %logger - %msg%n</pattern>
</encoder>
</appender>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</springProfile>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
<springProfile> 로 dev·prod 환경 분기. 42편 Profiles 에서 깊이.
MDC — 요청별 추적 ID
여러 요청이 동시에 도는 서버에서 — "이 로그가 어느 요청 거?" 가 헷갈려요. MDC(Mapped Diagnostic Context) 가 답.
@Component
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String traceId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("traceId", traceId);
try {
chain.doFilter(req, res);
} finally {
MDC.clear(); // 요청 끝나면 정리 필수
}
}
}
로그 패턴에 %X{traceId} 박으면 — 모든 로그 줄에 자동으로 traceId 등장. 한 요청의 모든 로그를 검색으로 모을 수 있어요.
2026-05-17 12:30:45 INFO [a3f7b2c1] OrderController - 주문 생성 요청
2026-05-17 12:30:45 INFO [a3f7b2c1] OrderService - 주문 생성 시작 userId=42
2026-05-17 12:30:46 INFO [a3f7b2c1] OrderRepository - INSERT INTO orders
2026-05-17 12:30:46 INFO [a3f7b2c1] OrderController - 응답 완료
a3f7b2c1 한 ID로 "이 요청이 한 묶음" 임을 알 수 있어요. 운영 트러블슈팅의 핵심.
비밀번호·신용카드 번호·주민번호·API 키·JWT 토큰. 한 번 로그에 박히면 수 개월 보관·검색·외부 유출 위험. 마스킹(`****-****-****-1234`) 또는 아예 생략. 한국 회사 보안 사고의 대표 원인.
한 줄 정리 — SLF4J = 자바 로깅 표준 인터페이스, Logback = Spring Boot 기본 구현체. @Slf4j + log.info("{}", var) + 패키지별 레벨 설정. MDC로 요청 추적 ID 표준.
시험 직전 한 번 더 — 로깅 입문자가 매번 헷갈리는 것
- SLF4J = 자바 로깅 표준 인터페이스 (Facade)
- Logback = Spring Boot 기본 구현체
- 다른 구현체 = Log4j2·java.util.logging
- Spring Boot = 별도 의존성 없이 Logback 자동 포함
- Lombok
@Slf4j=private static final Logger log = ...자동 생성 - 로그 레벨 5단계 = ERROR > WARN > INFO > DEBUG > TRACE
- 운영 = INFO 이상, 개발 = DEBUG
{}placeholder 사용 — 문자열 연결보다 효율log.error("...", exception)= 마지막 인자가 Throwable이면 스택 자동application.yml의logging.level.<패키지>= 패키지별 레벨- 파일 출력 =
logging.file.name또는path - 롤링 =
max-file-size·max-history자동 logback-spring.xml= 세밀한 설정·환경 분기<springProfile>로 dev·prod 분기- MDC = 요청별 컨텍스트 (traceId·userId 등)
%X{key}로 로그 패턴에 박기- MDC는 요청 끝나면
MDC.clear()필수 - 민감 정보 로그 금지 = 비밀번호·카드·주민번호·토큰
- 마스킹 필수 또는 아예 생략
System.out.println절대 X — 우리 코드 =log.info- 운영 = 파일 + JSON 포맷 + ELK·Datadog 같은 중앙 로그
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 31편 — 파일 업로드 @RequestPart MultipartFile
- 32편 — CORS 설정
- 33편 — @ExceptionHandler @ControllerAdvice
- 34편 — Bean Validation @Valid @NotNull
- 35편 — 커스텀 Validator 만들기
다음 글: