[Spring Boot] Logging: Logback, Log4j2, Structured Logging
정의
Spring Boot 의 기본 로거는 Logback (spring-boot-starter 가 자동 포함). SLF4J facade 위에서 동작.
교체 가능:
spring-boot-starter-log4j2: Log4j2 (높은 처리량, async logger)java.util.logging: 비권장- SLF4J adapter 로 commons-logging 흡수
대부분 그대로 Logback 사용. 운영 환경에서는 structured JSON 로깅 + MDC + 외부 수집기 (ELK, Loki, CloudWatch) 와 조합.
기본 application.yml 설정
logging:
level:
root: INFO
com.example: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE
pattern:
console: "%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
file:
name: ./logs/app.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
total-size-cap: 1GB
file-name-pattern: "./logs/app-%d{yyyy-MM-dd}.%i.log"
대부분 use case 는 yml 만으로 충분. xml 은 복잡한 패턴 / appender 필요할 때.
logback-spring.xml (고급)
src/main/resources/logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_PATH" value="./logs"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>2048</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
</springProfile>
</configuration>
핵심:
<springProfile>: profile 별 설정AsyncAppender: I/O 가 핫 스레드 안 막음 (운영 권장)SizeAndTimeBasedRollingPolicy: 크기 + 날짜 동시 rollover.gz확장자: 자동 압축
Structured JSON Logging (운영 권장)
Plain text 로그는 grep 으로 분석. JSON 은 ELK / Loki / Datadog 같은 수집기가 native 파싱.
Spring Boot 3.4+ 내장 지원
logging:
structured:
format:
console: ecs # Elastic Common Schema
file: ecs
옵션: ecs, gelf, logstash. 코드 변경 없이 JSON 전환.
Logstash encoder (수동)
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<fieldNames>
<timestamp>@timestamp</timestamp>
<message>message</message>
<level>level</level>
</fieldNames>
<customFields>{"app":"my-app","env":"prod"}</customFields>
<includeMdc>true</includeMdc>
</encoder>
</appender>
출력 예:
{"@timestamp":"2026-06-23T10:00:00.123Z","level":"INFO","logger_name":"c.e.OrderService","thread_name":"http-nio-8080-exec-1","message":"order placed","app":"my-app","env":"prod","mdc":{"traceId":"abc123","userId":"42"}}
MDC (Mapped Diagnostic Context)
스레드별 컨텍스트 (request id, user id 등). 자동으로 모든 로그에 첨부:
import org.slf4j.MDC;
@Component
public class MdcFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws IOException, ServletException {
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("clientIp", req.getRemoteAddr());
try {
chain.doFilter(req, resp);
} finally {
MDC.clear(); // 반드시 정리 (ThreadLocal 누수 방지)
}
}
}
pattern 에서 사용:
pattern: "%d{HH:mm:ss.SSS} [%X{requestId}] %-5level %logger{36} - %msg%n"
%X{key} 가 MDC 값. 같은 요청의 모든 로그가 requestId 공유 → 추적 용이.
OpenTelemetry / Micrometer Tracing 통합
Spring Boot 3+ 의 자동 통합:
management:
tracing:
sampling:
probability: 1.0
endpoints:
web:
exposure:
include: prometheus
traceId, spanId 가 자동으로 MDC 에 추가. 모든 로그에 분산 추적 ID 첨부. Tempo / Jaeger / Zipkin 으로 수집.
@Async 와 MDC
@Async 메서드는 별도 스레드 → MDC 비어 있음. TaskDecorator 로 복사:
public class MdcCopyDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
try {
if (context != null) MDC.setContextMap(context);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
@Bean
TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setTaskDecorator(new MdcCopyDecorator());
exec.initialize();
return exec;
}
Log4j2 교체
implementation("org.springframework.boot:spring-boot-starter") {
exclude(module = "spring-boot-starter-logging")
}
implementation("org.springframework.boot:spring-boot-starter-log4j2")
log4j2-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] %c{1} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Log4j2 장점:
- async logger 가 logback 보다 빠름 (Disruptor 기반)
- LMAX queue 로 zero allocation
- 정형식 message:
logger.info("user {} placed order {}", userId, orderId)가 lazy 평가
컨테이너 / K8s 환경
logging:
pattern:
console: "%d{ISO8601} %-5level [%thread] %logger{36} - %msg%n"
level:
root: INFO
file:
name: # 빈 값 = 파일 출력 비활성
K8s 패턴: stdout 만 출력. 컨테이너가 자체 수집 (Fluentd, Promtail). 파일 로깅 + sidecar 패턴은 deprecated.
비밀번호 / 민감 정보 masking
logger.info("Login attempt: user={}, password={}", username, password); // ❌ 평문 노출
해결:
- 로그 메시지에서 명시적으로 제외
@JsonIgnore(직렬화 시) +@ToString.Exclude(Lombok)- Logback 의 filter 로 정규식 마스킹
<appender name="MASKED" class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%replace(%msg){'(password|ssn)=\\S+', '$1=***'}%n</pattern>
</appender>
동적 log level 변경 (Actuator)
management:
endpoints:
web:
exposure:
include: loggers
# 현재 level 조회
curl http://localhost:8080/actuator/loggers/com.example
# 변경
curl -X POST http://localhost:8080/actuator/loggers/com.example \
-H "Content-Type: application/json" \
-d '{"configuredLevel":"DEBUG"}'
재시작 없이 운영 중 디버그 토글. (spring-actuator 참고)
함정과 베스트 프랙티스
- 운영: structured JSON + 수집기: plain text 분석은 한계
- MDC 는 try/finally 로 clear: ThreadLocal 누수 방지
@Async/ virtual thread 와 MDC: TaskDecorator 필수- AsyncAppender 운영 권장: I/O 가 핫 스레드 막지 않게
- password / secret 절대 로그 금지: 정규식 마스킹 안전망
- 개발: DEBUG, 운영: INFO/WARN: profile 별 분리
<springProfile>활용: logback xml 안에서 환경 분기- 컨테이너: stdout only: 파일 / sidecar 패턴 deprecated
- 로그 분산 추적 (traceId) 가 modern 운영 표준
관련 위키
이 글의 용어 (5개)
- [Java] ThreadLocaljava
- 정의 는 각 스레드마다 독립된 변수 사본을 가지게 해 주는 컨테이너. 로 접근하며, 한 스레드의 값은 다른 스레드에서 볼 수 없다. 쓰임은 두 가지: (1) 같은 인스턴스를 여러…
- [Spring Boot] Properties 외부화: PropertySource, Profile, @ConfigurationPropertiesspring
- 정의 Spring Boot 는 설정값을 15가지 소스에서 우선순위로 병합. 같은 키가 여러 곳에 있으면 우선순위 높은 게 이김. 코드 변경 없이 환경별 설정 가능. 핵심 소스 (…
- [Spring] @Scheduled와 @Asyncspring
- 정의 Spring은 @Scheduled (정기 작업)와 @Async (비동기 메서드)를 어노테이션으로 제공. ThreadPool, TaskScheduler 설정 가능. Quart…
- [Spring] Actuator: health, metrics, infospring
- 정의 Spring Boot Actuator는 프로덕션용 monitoring/management endpoint 자동 제공. health check, metrics, env, be…
- [Spring] RequestContextHolder, LocaleContextHolder: ThreadLocal 기반 요청 컨텍스트spring
- 정의 Spring 은 HTTP 요청 / 응답 / locale / 트랜잭션 / 인증 같은 요청 범위 데이터를 ThreadLocal 에 저장 해 전역 노출한다. 메서드 인자로 일일이…
💬 댓글