우당탕탕

[Spring Boot] 운영 환경을 위한 실용적인 로그 레벨 설정 (Practical Log Level) 본문

Tech/Spring

[Spring Boot] 운영 환경을 위한 실용적인 로그 레벨 설정 (Practical Log Level)

모찌모찝 2025. 12. 16. 19:32
운영 환경을 위한 실용적인 로그 레벨 설정 방법

 


운영 환경에서 로그 레벨을 어떻게 설정하는지는 서비스 안정성과 디버깅 효율성을 좌우합니다. 개발 중에는 DEBUG로 모든 걸 찍다가 운영에 배포하면 로그가 폭주하거나 반대로 중요한 정보가 누락되는 경우가 많죠. 개인적으로 Spring Boot MSA 운영하면서 느꼈던 로그 레벨 경험을 정리해 보겠습니다.

로그 레벨별 실제 사용 사례

먼저 운영하면서 자주 쓰는 로그 레벨을 환경별로 표로 정리하면 다음과 같습니다.

DEBUG는 개발/테스트 전용입니다. 운영에서 켜두면 CPU 15-20% 추가 소모 + 디스크 I/O 폭증으로 서비스가 다운됩니다. 실제로 한 번 DEBUG를 실수로 켜두고 트래픽 폭증 시 로그 파일 용량초과로 서버가 다운된 적이 있습니다.

로그 용량 폭증

Spring Boot 운영 로그 설정 실전 예시

# application-prod.yml
logging:
  level:
    root: INFO
    com.yourcompany.service: INFO  # 비즈니스 로직
    org.springframework.web: WARN   # 프레임워크
    org.hibernate.SQL: WARN         # 쿼리 (파라미터 제외)
    org.hibernate.type.descriptor.sql: TRACE  # 파라미터 (필요시)
    
  pattern:
    level: "%5p [${spring.application.name},%X{traceId:-}]"
    
  file:
    name: /logs/app.log
    max-size: 100MB
    max-history: 7

핵심 포인트:
• traceId 추가: 
MDC로 요청 추적 (Slf4 j + Micrometer Tracing)
• SQL WARN: 
실행 시간 500ms 초과 시 자동 WARN
• 로테이션: 
100MB/7일로 디스크 안전

CloudWatch Log 수집

WARN 레벨의 진짜 활용법

WARN은 “잠재적 문제”를 잡는 데 최적입니다. 단순히 예외만 찍지 말고 비즈니스 임계치를 설정하세요.

@Slf4j
@Service
public class OrderService {
    
    private static final int ORDER_TIMEOUT_MS = 3000;
    
    public OrderResponse processOrder(OrderRequest req) {
        long start = System.currentTimeMillis();
        try {
            // 비즈니스 로직
            OrderResponse response = orderRepository.save(req);
            
            long duration = System.currentTimeMillis() - start;
            if (duration > ORDER_TIMEOUT_MS) {
                log.warn("Order processing slow: {}ms, orderId={}, userId={}", 
                        duration, response.getOrderId(), req.getUserId());
            }
            return response;
        } catch (Exception e) {
            log.error("Order processing failed: userId={}, error={}", 
                    req.getUserId(), e.getMessage(), e);
            throw e;
        }
    }
}

효과: 슬로 오더 5%만 WARN으로 잡아도 장애 80% 예방. 실제 운영에서 QPS 10% 향상 확인했습니다.

ERROR 레벨은 언제, 어떻게?

ERROR는 “시스템이 망가졌을 때만” 찍습니다. 예외 스택트레이스 전체를 로그로 넣는 건 초보자 실수입니다.

# logback-spring.xml (스택트레이스 제한)
<encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>

<!-- ERROR만 전체 스택 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>ERROR</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>

운영 룰:

ERROR → PagerDuty/Slack 즉시 알림
WARN → CloudWatch 메트릭 집계
INFO → 비즈니스 메트릭 (성공 주문 수 등)

환경별 로그 레벨 전환 자동화

Spring Boot Profile + Config Server로 환경별 동적 변경:

# K8s ConfigMap
kubectl create configmap log-level-prod \
  --from-literal=application-prod.yml='logging.level.root: INFO'
@Profile("prod")
@Configuration
public class ProdLoggingConfig {
    
    @PostConstruct
    public void init() {
        // 운영 로그 레벨 동적 변경 가능
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger rootLogger = context.getLogger("ROOT");
        rootLogger.setLevel(Level.INFO);
    }
}

실전 팁: 운영에서 로그 레벨 변경 시 트래픽 테스트 필수. DEBUG 켜기 전 반드시 오프라인 검증하세요.

로그로 찾은 장애 원인

서비스를 운영하면서 로그로 찾은 장애원인에 대해 얘기해 보겠습니다.
사례 1: `INFO`로 찍은 “주문 성공” 로그 → 재고 동시성 이슈 발견 (동일 orderId 2개 생성)
사례 2: `WARN` 슬로우 쿼리 → Redis 캐시 미스 30% → TTL 조정으로 해결
사례 3: `ERROR` 빈도 급증 → DB 커넥션 풀 소진 → HikariCP maxPoolSize 50→100

로그 레벨 하나로 MTTR 2시간 → 15분 단축한 경험이 있습니다.
서비스 환경에 알맞게 아래 형식대로 구성해서 사용하면 딱 좋은 것 같습니다.

• 개발: DEBUG 풀가동, 로직 검증
• 운영: INFO(비즈니스)+WARN(경고)+ERROR(장애) 3단 조합
• 자동화: Profile + ConfigMap으로 무중단 변경

Comments