로깅 시스템 기초: 파일 로그, 콘솔 로그, CloudWatch Logs 연동

CH.dev·2025년 7월 27일
post-thumbnail

📄 요약

개발 초기: 콘솔 로그
데이터 영속: 파일 로그,
중앙 집중 관리 및 분석: AWS CloudWatch Logs
Spring Boot 환경에서 SLF4J와 Logback을 활용한 환경별(Profile) 설정, MDC를 통한 추적성 확보, 구조화된 로깅(Structured Logging) 등 실무적인 기법까지 포함하여 견고한 로깅 아키텍처 구축 방법 공부

💡 주요 개념

1단계: 콘솔 로그 (Console Log) - 즉각적인 피드백

  • 정의: 애플리케이션 실행 시 표준 출력(stdout)으로 로그를 표시하는 가장 원시적이고 즉각적인 방식.
  • 장점: 개발 환경에서 코드 실행 흐름, 변수 상태를 디버거 없이 빠르게 확인하는 데 매우 유용함.
    Spring Boot는 별도 설정 없이 기본으로 INFO 레벨 이상의 콘솔 로그를 출력해 개발 편의성을 높임.
  • 한계:
    • 휘발성: 애플리케이션이 종료되면 모든 로그 데이터가 소실됨.
    • 확장성 부재: 여러 서버 인스턴스에서 발생하는 로그를 통합하여 볼 수 없음.
    • 성능: 대량의 로그를 동기적으로 콘솔에 출력하는 작업은 애플리케이션 성능에 어느 정도 영향을 줄 수 있음.

2단계: 파일 로그 (File Log) - 데이터의 영속성 확보

  • 정의: 로그를 휘발성 메모리가 아닌, 지정된 파일 시스템에 저장하여 영속성을 확보하는 방식. Logback, Log4j2와 같은 로깅 구현체를 통해 파일 경로, 출력 형식, 아카이빙 정책 등을 세밀하게 제어함.
  • 핵심 기능 - Rolling Policy:
    • 로그 파일이 무한정 커지는 것을 방지하고 관리를 용이하게 하기 위해 파일을 분리 저장하는 정책. 운영 환경의 필수 요소.
    • TimeBasedRollingPolicy: 날짜(일, 시간) 기준으로 파일을 분리. (예: app-2023-10-27.log)
    • SizeAndTimeBasedRollingPolicy: 날짜 기준에 추가로 파일 크기 제한을 두어, 특정 용량 초과 시 같은 날짜 안에서도 파일을 분리. (예: app-2023-10-27.0.log)
  • 한계:
    • 접근성: 로그를 확인하려면 SSH 등을 통해 서버에 직접 접속해야 하는 번거로움이 있음.
    • 중앙 관리의 어려움: MSA(Microservice Architecture)나 스케일 아웃된 환경에서 파편화된 로그를 한눈에 파악하고 분석하기 매우 어려움.

3단계: CloudWatch Logs 연동 - 중앙 집중 관리 및 분석

  • 정의: 각 서버에 기록된 파일 로그를 CloudWatch Agent가 수집하여 AWS CloudWatch Logs로 전송, 중앙에서 모든 로그를 통합 관리/검색/분석/시각화하는 방식.
  • 아키텍처: 애플리케이션 → 파일 로그 → CloudWatch Agent → CloudWatch Logs
    • 이 구조는 애플리케이션과 로그 전송 로직을 완벽히 분리(Decoupling)하여, 로그 전송 실패가 애플리케이션에 영향을 주지 않도록 보장함. 애플리케이션은 오직 파일에 로그를 쓰는 책임만 가짐.
  • 장점:
    • 중앙 집중화: 모든 서버, 모든 애플리케이션의 로그를 단일 창구에서 관리.
    • 강력한 검색 및 분석: CloudWatch Logs Insights를 통해 복잡한 쿼리로 특정 조건의 로그를 신속하게 필터링하고 분석 가능.
    • 능동적 모니터링 및 알림: Metric Filter로 "ERROR"와 같은 특정 패턴의 빈도를 측정하고, Alarm을 설정하여 임계치 초과 시 Slack, 이메일 등으로 즉시 알림을 받아 장애에 선제적으로 대응 가능.
  • 한계:
    • 비용: 신중하게 관리하지 않는다면 비용이 빠르게 누적될 수 있음
    • 복잡성: 리소스가 많은 대규모 환경에서는 관리 및 구성이 복잡할 수 있음
    • AWS 학습 곡선 및 종속성: 익숙하지 않다면 어렵고, 하이브리드 환경 모니터링에 어려움

🧠 코드

1. Logback 설정 (logback-spring.xml) - 환경별(Profile) 분리

dev, local 환경에서는 콘솔로,
prod 환경에서는 파일로 로그를 남기도록 분리하는 것이 실무적.
Spring Boot의 'Profile' 기능을 활용함.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 공통 설정: 로그 파일 경로 및 패턴 정의 -->
    <property name="LOG_PATH" value="./logs"/>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n"/>

    <!-- CONSOLE Appender -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- FILE Appender (Rolling Policy 적용) -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/app.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/archived/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap> <!-- 전체 로그 최대 10GB 유지 -->
        </rollingPolicy>
    </appender>

    <!-- 개발/로컬 환경: 콘솔에만 출력 -->
    <springProfile name="dev, local">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!-- 운영 환경: 파일에만 출력 (콘솔 출력은 성능 저하 유발 가능) -->
    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

</configuration>

2. AWS CloudWatch Agent 설정 (config.json) - 타임스탬프 파싱

로그 라인에 포함된 시간으로 CloudWatch 로그 시간을 설정해야 정확한 시간대별 조회가 가능함. timestamp_format 설정이 중요.

{
  "agent": {
    "run_as_user": "root"
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/home/ec2-user/app/logs/app.log",
            "log_group_name": "my-prod-app-logs",
            "log_stream_name": "{instance_id}",
            "timezone": "Asia/Seoul",
            "timestamp_format": "%Y-%m-%d %H:%M:%S.%f"
          }
        ]
      }
    }
  }
}
  • file_path: logback-spring.xml에 설정된 로그 파일의 절대 경로 지정.
  • log_stream_name: {instance_id}로 설정 시, 각 EC2 인스턴스별로 로그 스트림이 자동 생성되어 서버별 로그 추적이 용이함.
  • timestamp_format: LOG_PATTERN의 날짜/시간 형식과 정확히 일치시켜야 함.
  • EC2 인스턴스에는 CloudWatch Agent가 로그를 전송할 수 있도록 CloudWatchAgentServerPolicy 권한이 포함된 IAM Role이 연결되어 있어야 함.

3. Spring Boot 로그 생성 예시 - MDC로 추적 ID 추가

MDC(Mapped Diagnostic Context)를 사용하면 요청 단위로 고유 ID(예: traceId)를 부여하여, 특정 요청과 관련된 모든 로그를 쉽게 필터링할 수 있음.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

@RestController
public class LogController {

    private static final Logger logger = LoggerFactory.getLogger(LogController.class);

    @GetMapping("/log")
    public String generateLogs() {
        // HTTP 요청 시작 시점에 Trace ID를 MDC에 추가
        MDC.put("traceId", UUID.randomUUID().toString().substring(0, 8));

        logger.info("사용자 요청 처리 시작.");
        logger.warn("중요 파라미터 확인 필요.");
        
        try {
            // 다른 서비스 호출이나 복잡한 로직 수행
            processSomething();
        } catch (Exception e) {
            logger.error("심각한 에러 발생! 원인 분석 필요.", e);
        } finally {
            // 요청 처리가 끝나면 MDC에서 Trace ID 제거
            MDC.clear();
        }
        
        return "Logs generated and sent to CloudWatch!";
    }
    
    private void processSomething() {
        logger.info("내부 로직 수행 중...");
        if (System.currentTimeMillis() % 2 == 0) {
            throw new RuntimeException("Simulated internal error");
        }
    }
}
  • 위 코드는 FilterInterceptor를 사용하면 모든 요청에 대해 traceId를 자동으로 부여하고 제거하도록 개선할 수 있음.

🔍 더 알아보기

  1. Structured Logging (JSON 형식 로깅): 로그를 key:value 형태의 JSON으로 구조화하는 기법. logstash-logback-encoder 라이브러리를 사용하면 쉽게 구현 가능.

    • 장점: CloudWatch Logs Insights에서 json.level = 'ERROR' 와 같이 필드 기반의 정교한 쿼리가 가능해져 분석 효율이 극대화됨.
    • 예시 logback-spring.xml 설정:
      <appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>${LOG_PATH}/app.json.log</file>
          <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
          ...
      </appender>
  2. SLF4J (Simple Logging Facade for Java)의 본질:

    • SLF4J는 로깅에 대한 표준 인터페이스(Facade). Logger, LoggerFactory가 여기에 속함.
    • Logback, Log4j2는 그 인터페이스의 실제 구현체(Implementation).
    • JDBC API(인터페이스)와 MySQL/PostgreSQL Driver(구현체)의 관계와 동일. 이 구조 덕분에 애플리케이션 코드 변경 없이 로깅 구현체를 자유롭게 교체할 수 있음.
  3. CloudWatch Logs Insights 쿼리:

    • 수집된 로그를 분석하기 위한 강력한 쿼리 언어.
    • 쿼리 예시: 최근 1시간 동안 발생한 ERROR 로그 중, 특정 traceId를 가진 로그들을 시간 순으로 정렬하여 메시지와 예외 정보를 확인.
      fields @timestamp, @message, `exception.message`
      | filter level = 'ERROR' and traceId = 'a1b2c3d4'
      | sort @timestamp desc
      | limit 20
profile
더 이상 미룰 수 없다 나의 공부 나의 성장

0개의 댓글