안녕하세요! 프로덕션 환경에서 Spring Boot 애플리케이션을 운영하고 계신가요? 로그 관리가 얼마나 중요한지 알고 계시죠? 마치 블랙박스처럼 문제가 발생했을 때 무슨 일이 일어났는지 알려주는 유일한 단서가 바로 로그니까요!
등장 배경
과거에는 개발자들이 System.out.println()
으로 디버깅하던 시절이 있었어요. 😅 애플리케이션이 복잡해지면서 더 체계적인 로깅이 필요해졌죠. Spring Boot는 이런 문제를 해결하기 위해 강력한 로깅 프레임워크를 기본 제공하게 되었습니다.
초기에는 Log4j를 사용했지만, 성능과 유연성 문제로 Logback이 등장했고, 현재는 Log4j2까지 선택할 수 있게 되었습니다. 특히 프로덕션 환경에서는 다음과 같은 문제들을 해결해야 했습니다:
- 분산 시스템 환경: 마이크로서비스 아키텍처에서 여러 서비스의 로그를 어떻게 추적할 것인가?
- 대용량 처리: 초당 수천 건의 로그를 어떻게 효율적으로 처리할 것인가?
- 검색과 분석: 방대한 로그에서 문제를 어떻게 빠르게 찾을 것인가?
핵심 원리
로깅 아키텍처 시각화 🎯
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ Application │────▶│ SLF4J │────▶│ Logback/ │
│ (Your Code) │ │ (Facade) │ │ Log4j2 │
└─────────────────┘ └──────────────┘ └─────────────┘
│
▼
┌──────────────────────────────┐
│ Appenders │
├──────────────────────────────┤
│ • Console │
│ • File (Rolling) │
│ • Async │
│ • Network (TCP/UDP) │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ Log Destinations │
├──────────────────────────────┤
│ • Local Files │
│ • ELK Stack │
│ • Cloud Services │
└──────────────────────────────┘
주요 컴포넌트 비교
컴포넌트 | 역할 | 프로덕션 추천 설정 |
---|---|---|
Logger | 로그 메시지 생성 | 패키지별 레벨 설정 |
Appender | 로그 출력 위치 결정 | RollingFileAppender + AsyncAppender |
Layout/Encoder | 로그 형식 지정 | JSON 형식 (구조화된 로깅) |
Filter | 로그 필터링 | ThresholdFilter, MDC 기반 필터 |
프로덕션 환경 설정 가이드 🚀
1. 구조화된 로깅 (JSON 형식) 설정
Spring Boot 3.4부터는 네이티브 구조화된 로깅을 지원합니다:
# application.yml
logging:
structured:
format:
console: ecs # Elastic Common Schema
file: logstash # 파일은 Logstash 형식으로
file:
name: ${LOG_PATH:./logs}/application.log
# ECS 커스터마이징
structured:
ecs:
service:
name: ${spring.application.name}
version: ${info.app.version}
environment: ${ENVIRONMENT:production}
node-name: ${HOSTNAME:unknown}
2. 롤링 파일 설정 (logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 프로퍼티 정의 -->
<property name="LOG_PATH" value="${LOG_PATH:-./logs}"/>
<property name="LOG_FILE" value="${LOG_FILE:-application}"/>
<!-- 콘솔 출력용 (개발 환경) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 롤링 파일 어펜더 -->
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<!-- 추가 필드 설정 -->
<customFields>{"service":"${spring.application.name}","env":"${ENVIRONMENT}"}</customFields>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 일별 롤오버 + 파일 크기 제한 -->
<fileNamePattern>${LOG_PATH}/archived/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 비동기 어펜더로 성능 향상 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="ROLLING_FILE"/>
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
<neverBlock>false</neverBlock>
</appender>
<!-- 프로파일별 설정 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="!dev">
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
<!-- 성능을 위한 세밀한 레벨 조정 -->
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="com.mycompany.critical" level="DEBUG"/>
</springProfile>
</configuration>
3. ELK 스택 연동 설정
TCP 소켓을 통한 직접 전송:
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOGSTASH_HOST:localhost}:${LOGSTASH_PORT:5000}</destination>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<threadName/>
<logger/>
<message/>
<stackTrace/>
<mdc/> <!-- MDC 데이터 포함 -->
<context/>
<pattern>
<pattern>
{
"service": "${spring.application.name}",
"instance": "${HOSTNAME}",
"version": "${info.app.version}"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
4. MDC를 활용한 추적성 향상
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 요청별 고유 ID 생성
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
MDC.put("userId", getUserId(request));
MDC.put("clientIp", request.getRemoteAddr());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// MDC 정리
MDC.clear();
}
}
5. 성능 최적화 설정
# application-production.yml
logging:
level:
# 루트 레벨은 WARN으로 설정
root: WARN
# 중요한 패키지만 INFO 레벨
com.mycompany.app: INFO
# 크리티컬한 부분은 DEBUG
com.mycompany.app.critical: DEBUG
# 패턴 최적화 (필요한 정보만 포함)
pattern:
file: "%d{ISO8601} [%thread] %-5level %logger{35} - %msg%n"
# 로그 파일 설정
file:
name: ${LOG_PATH}/app.log
max-size: 100MB
max-history: 30
total-size-cap: 3GB
주의사항 및 팁 💡
⚠️ 이것만은 주의하세요!
민감한 정보 로깅 금지
- 비밀번호, API 키, 개인정보는 절대 로깅하지 마세요
- 필요시 마스킹 처리:
logger.info("User {} logged in", maskEmail(email))
로그 레벨 남용 금지
- ERROR: 즉시 조치가 필요한 경우만
- WARN: 잠재적 문제 상황
- INFO: 중요한 비즈니스 이벤트
- DEBUG: 개발/디버깅용 (프로덕션에서는 비활성화)
비동기 로깅 주의사항
- 애플리케이션 종료 시 버퍼의 로그가 유실될 수 있음
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
추가
💡 꿀팁
- 로그 집계 도구 활용: ELK, Splunk, Datadog 등으로 중앙화
- 상관관계 ID 사용: 분산 환경에서 요청 추적을 위해 Spring Cloud Sleuth 활용
- 메트릭과 함께 사용: Micrometer + Prometheus로 로그와 메트릭 연계
- 로그 샘플링: 대용량 환경에서는 일부만 로깅하여 성능 향상
마치며
지금까지 Spring Boot의 프로덕션 로깅 설정에 대해 알아보았습니다. 처음에는 복잡해 보일 수 있지만, 체계적인 로깅은 장애 대응 시간을 획기적으로 단축시켜줍니다!
여러분의 애플리케이션에 맞는 로깅 전략을 수립하고, 지속적으로 개선해 나가시길 바랍니다. 특히 클라우드 환경에서는 로그 관리가 더욱 중요하니, ELK나 클라우드 네이티브 솔루션을 적극 활용해보세요! 🚀
참고 자료 🔖
#SpringBoot #Logging #Production #ELKStack #Logback
728x90
반응형
'100===Dev Ops > Server Monitoring' 카테고리의 다른 글
Spring Boot Log4j2 최적화 설정 가이드 (2) | 2025.06.19 |
---|---|
Pinpoint 완벽 가이드 - 대규모 분산 시스템을 위한 API 모니터링 도구 🔍 (1) | 2025.05.15 |