DevOps

Spring boot 에서 CloudWatch 로그 수집 (feat. Profile 별로)

southouse 2022. 8. 25. 12:02
728x90

기존에 회사에서 애플리케이션 로그를 수집하는 방식은 애플리케이션 서버에 일주일 로그를 저장을 하고 매 주마다 로그를 저장하는 원격 서버로 파일을 저장하는 명령어인 scp를 통해 저장하고 로그 정책에 따라 shell 스크립트를 이용해 저장 기간이 지난 로그를 삭제하는 구성이었다.

 

로그를 원격지에 소산하는 것에는 큰 문제가 없었지만, 생각보다 스크립트를 작성하고 관리하는 측면에서 꽤나 여러가지 문제가 있었다. 예를 들면, 사전에 충분히 테스트가 되지 않아 스크립트 자체에 문제가 있는 경우 혹은 서버 레벨에서 cron을 통해 주기적으로 스크립트를 실행했는데 cron이 올바르게 동작하지 않는다든가, 꽤 관리적 측면에서 많이 신경을 써줘야 했다.

 

그래서 간단한 설정으로 Spring Boot 애플리케이션 자체에서 AWS CloudWatch로 로그를 전송하여 수집하는 설정을 해보고자 한다.

실습 환경

  • AWS
    • IAM
    • CloudWatch
    • CLI
  • Java 18 (corretto)
  • Spring Boot 2.7.3
    • logback
  • Gradle 2.7.3

코드 작성

의존성 추가

// https://mvnrepository.com/artifact/ca.pjer/logback-awslogs-appender
implementation group: 'ca.pjer', name: 'logback-awslogs-appender', version: '1.6.0'

로그 생성 클래스

Spring Boot 로그 레벨은 총 5가지로, INFO, WARN, ERROR, DEBUG, TRACE로 구분되는데, 여기에선 자세히 다루지 않을 예정이라, 로그 레벨 및 logback 구성에 관련된 내용은 맨 아래 Reference에 있는 포스팅을 통해 학습하면 된다.

@EnableScheduling
@SpringBootApplication
public class CloudwatchLogExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudwatchLogExampleApplication.class, args);
    }

}

Scheduling을 사용하기 위해 Spring Boot 애플리케이션을 생성하는 main 메소드에 @EnableScheduling 어노테이션을 설정한다.

@Slf4j
@Component
public class LogSchedule {

    @Scheduled(cron = "*/10 * * * * *")
    private void printInfoMessage() {
        log.info("일반 메세지 출력");
    }

    @Scheduled(cron = "0 * * * * *")
    private void printErrorMessage() {
        log.error("에러 메세지 출력");
    }

    @Scheduled(cron = "*/30 * * * * *")
    private void printWarnMessage() {
        log.warn("Warning 메세지 출력");
    }

}

INFO 로그는 10초마다, WARN 로그는 30초마다, ERROR 로그는 1분마다 찍기 위해 Scheduling 클래스를 생성한다. 해당 코드만 작성하고 애플리케이션을 실행하면, 스케줄링 설정에 따라 아래와 같은 로그가 계속 찍히게 된다.

2022-08-25 11:39:00.013  WARN 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : Warning 메세지 출력
2022-08-25 11:39:10.002  INFO 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 일반 메세지 출력
2022-08-25 11:39:20.005  INFO 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 일반 메세지 출력
2022-08-25 11:39:30.005  INFO 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 일반 메세지 출력
2022-08-25 11:39:30.007  WARN 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : Warning 메세지 출력
2022-08-25 11:39:40.002  INFO 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 일반 메세지 출력
2022-08-25 11:39:50.001  INFO 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 일반 메세지 출력
2022-08-25 11:40:00.002 ERROR 53516 --- [   scheduling-1] c.s.cloudwatchlogexample.LogSchedule   : 에러 메세지 출력

logback 설정 파일

<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <appender name="dailyRollingFileAppender"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <prudent>true</prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>./logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{HH:mm:ss.SSS} [%thread] [%5level] %logger{35}[%method:%line] %m%n</pattern>
        </encoder>
    </appender>
    <appender name="dailyCloudwatchAppender"
              class="ca.pjer.logback.AwsLogsAppender">
        <layout>
            <pattern>[%thread] [%date] [%level] [%file:%line] - %msg%n</pattern>
        </layout>
        <logGroupName>/aws/ec2/instance/cloudwatch-log-example/application-log</logGroupName>
        <logStreamUuidPrefix>cloudwatch-log-example-</logStreamUuidPrefix>
        <logRegion>ap-northeast-2</logRegion>
        <maxBatchLogEvents>50</maxBatchLogEvents>
        <maxFlushTimeMillis>30000</maxFlushTimeMillis>
        <maxBlockTimeMillis>5000</maxBlockTimeMillis>
        <retentionTimeDays>0</retentionTimeDays>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{HH:mm:ss.SSS} [%thread] [%5level] %logger{35}[%method:%line] %m%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="dailyRollingFileAppender"/>
        <appender-ref ref="dailyCloudwatchAppender"/>
    </root>
</configuration>

해당 설정의 property에 대해 간략히 설명하자면,

  • logGroupName
    CloudWatch 로그 그룹의 이름
  • logStreamUuidPrefix
    CloudWatch 로그 스트림 접두사
  • logRegion
    AWS 리전
  • maxBatchLogEvents
    배치의 최대 이벤트 개수 설정, 1 ~ 10000 사이 값만 설정 가능
  • maxFlushTimeMillis
    마지막 플러스가 발생되고 지정된 시간이 지나면 로그를 전송, 0일 경우에는 동기, 0 이상의 값이면 비동기로 로그 전송
  • maxBlockTimeMillis
    로그가 전송되는 동안 코드가 계속 실행되는 것을 차단, 0으로 설정하면 모든 로그를 버리게 됨
  • retentionTimeDays
    CloudWatch 로그 그룹의 보존 기간, 0일 경우에 무기한 보존
  • accessKeyId, secretAccessKey
    CloudWatch로 로그를 전송할 IAM 계정의 Key

나머지 설정은 dailyRollingFileAppender는 로컬에 ./logs/application.%d{yyyy-MM-dd}.log 해당 경로에 로그를 지정하는 설정이고, dailyCloudwatchAppender는 로그를 CloudWatch로 전송하기 위한 설정이다.

 

따라서, 로컬에도 저장하고 CloudWatch에도 로그를 전송하게 된다. 이 설정은 필요에 따라 로컬에는 저장 안 해도 된다.

AWS 설정

IAM 계정 키 발급

IAM 사용자 추가


IAM 액세스 키를 사용할 예정이기 때문에, 액세스 키 - 프로그래밍 방식 액세스 를 체크하여 다음으로 넘어간다.

IAM 사용자 추가 > 권한 설정

❯ aws configure
AWS Access Key ID [None]: 액세스 키 입력
AWS Secret Access Key [None]: 시크릿 키 입력
Default region name [None]: ap-northeast-2
Default output format [None]: json

생성한 IAM 계정의 액세스 키와 비밀 액세스 키를 aws-cli 를 이용하여 현재 PC에 등록한다.

 

참고로 지금은 IAM 계정을 발급 받아 사용하지만, 나중에 애플리케이션을 EC2 혹은 ECS와 같은 AWS 컴퓨팅 리소스에 배포하게 된다면 보안 상 액세스 키를 발급 받아 사용하기 보단 IAM 역할을 등록하여 사용하는 편이 좋다.

AWS CloudWatch 확인

자, 이제 애플리케이션 실행 후 AWS 콘솔로 들어가 CloudWatch에 저장된 로그를 확인해보도록 하겠다. 아까 위에서 코드를 실행했을 때와 동일한 로그가 CloudWatch 로그 그룹 / 로그 스트림에 저장이 되어 있어야 한다.

 

AWS CloudWatch > 로그 그룹 > /aws/ec2/instance/cloudwatch-log-example/application-log 로 들어가면 아래와 같이 위의 logback 설정 파일의 이름대로 로그 그룹 / 로그 스트림이 생성되어 있다.

로그 그룹 세부 정보
로그 스트림 내용

로그 스트림을 클릭하여 들어가면 우리가 실행한 애플리케이션의 로그가 저장되어 있다.

참고로, 로그 그룹을 선택하고 작업 > 보존 설정 편집을 통해서 로그를 일정 기간 저장하고 삭제하는 설정도 쉽게할 수 있다.

Profile 별로 로그 설정

예를 들어, 개발환경에서 로그를 로컬에만 저장하고, 운영환경에서 로그를 CloudWatch로 전송하고자 한다면, 아래와 같이 logback 설정을 변경하면 된다. 단순하게 <SpringProfile name="{PROFILE_NAME}"> </SpringProfile>만 지정해주면 된다.

<configuration>
    <springProfile name="dev">
        <include resource="org/springframework/boot/logging/logback/base.xml"/>
        <appender name="dailyRollingFileAppender"
                  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <prudent>true</prudent>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>./logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <charset>UTF-8</charset>
                <pattern>%d{HH:mm:ss.SSS} [%thread] [%5level] %logger{35}[%method:%line] %m%n</pattern>
            </encoder>
        </appender>
    </springProfile>
    <springProfile name="prod">
        <appender name="dailyCloudwatchAppender"
                  class="ca.pjer.logback.AwsLogsAppender">
            <layout>
                <pattern>[%thread] [%date] [%level] [%file:%line] - %msg%n</pattern>
            </layout>
            <logGroupName>/aws/ec2/instance/cloudwatch-log-example/application-log</logGroupName>
            <logStreamUuidPrefix>cloudwatch-log-example-</logStreamUuidPrefix>
            <logRegion>ap-northeast-2</logRegion>
            <maxBatchLogEvents>50</maxBatchLogEvents>
            <maxFlushTimeMillis>30000</maxFlushTimeMillis>
            <maxBlockTimeMillis>5000</maxBlockTimeMillis>
            <retentionTimeDays>0</retentionTimeDays>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <encoder>
                <charset>UTF-8</charset>
                <pattern>%d{HH:mm:ss.SSS} [%thread] [%5level] %logger{35}[%method:%line] %m%n</pattern>
            </encoder>
        </appender>
    </springProfile>
    <root level="INFO">
        <appender-ref ref="dailyRollingFileAppender"/>
        <appender-ref ref="dailyCloudwatchAppender"/>
    </root>
</configuration>

더 디테일한 설정은 logback 설정 파일에 대해서 알아보면 된다.

로그 스트림을 일 단위로 생성할 수는 없을까?

확인해보니, 생성되는 로그 스트림은 애플리케이션 실행 때마다 새로 생성된다. 그래서 기존처럼 일 단위마다 로그 파일을 저장하듯이 일 단위마다 로그 스트림을 생성할 수 없다. (매일 애플리케이션을 다시 시작한다면 모를까..)

Reference

300x250