100===Dev Ops/Server Monitoring

Spring Boot 로그 관리 - 프로덕션 환경 완벽 가이드 📝

블로글러 2025. 5. 29. 09:04

안녕하세요! 프로덕션 환경에서 Spring Boot 애플리케이션을 운영하고 계신가요? 로그 관리가 얼마나 중요한지 알고 계시죠? 마치 블랙박스처럼 문제가 발생했을 때 무슨 일이 일어났는지 알려주는 유일한 단서가 바로 로그니까요!

등장 배경

과거에는 개발자들이 System.out.println()으로 디버깅하던 시절이 있었어요. 😅 애플리케이션이 복잡해지면서 더 체계적인 로깅이 필요해졌죠. Spring Boot는 이런 문제를 해결하기 위해 강력한 로깅 프레임워크를 기본 제공하게 되었습니다.

초기에는 Log4j를 사용했지만, 성능과 유연성 문제로 Logback이 등장했고, 현재는 Log4j2까지 선택할 수 있게 되었습니다. 특히 프로덕션 환경에서는 다음과 같은 문제들을 해결해야 했습니다:

  1. 분산 시스템 환경: 마이크로서비스 아키텍처에서 여러 서비스의 로그를 어떻게 추적할 것인가?
  2. 대용량 처리: 초당 수천 건의 로그를 어떻게 효율적으로 처리할 것인가?
  3. 검색과 분석: 방대한 로그에서 문제를 어떻게 빠르게 찾을 것인가?

핵심 원리

로깅 아키텍처 시각화 🎯

┌─────────────────┐     ┌──────────────┐     ┌─────────────┐
│  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

주의사항 및 팁 💡

⚠️ 이것만은 주의하세요!

  1. 민감한 정보 로깅 금지

    • 비밀번호, API 키, 개인정보는 절대 로깅하지 마세요
    • 필요시 마스킹 처리: logger.info("User {} logged in", maskEmail(email))
  2. 로그 레벨 남용 금지

    • ERROR: 즉시 조치가 필요한 경우만
    • WARN: 잠재적 문제 상황
    • INFO: 중요한 비즈니스 이벤트
    • DEBUG: 개발/디버깅용 (프로덕션에서는 비활성화)
  3. 비동기 로깅 주의사항

    • 애플리케이션 종료 시 버퍼의 로그가 유실될 수 있음
    • <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
반응형