본문 바로가기

AWS

CloudWatch에 앱 로그스트림 생성

Logback 이란?

logback은 스프링 오픈소스 logging 프레임워크로 slf4j 의 구현체입니다.

spring-boot-starter-web 에 있는 spring-boot-starter-logging에 기본으로 포함되어 있어서 별도의 라이브러리 추가가 불필요 합니다. 스프 링 부트에서는 application.xml에 properties값만 세팅해도 설정이 가능하지만 보통 상세 설정을 위해 logback-spring.xml 을 사용합니다. 로그 레벨은 총 5가지로 TRACE < DEBUG < INFO < WARN < ERROR 순으로 출력 레벨을 지정할 수 있습니다.

로깅의 주요 설정 요소는 공식 메뉴얼을 참고합니다.

https://logback.qos.ch/manual/index.html

 

 

logback-spring.xml 설정하기

간단하게 Logback AWSLogs appender 오픈소스 라이브러리를 사용해서 CoudWatch에 로그스트림을 생성할 수 있습니다.

https://github.com/pierredavidbelanger/logback-awslogs-appender

build.gradle에 depengency를 추가합니다.

implementation 'ca.pjer:logback-awslogs-appender:1.6.0'

 

configuration 태그 내에 스프링 프로퍼티에서 가져온 값과 로깅 출력 conversion 패턴을 설정을 해줍니다.

 

<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<property name="LOG_PATTERN" value="${LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){blue} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:
- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<timestamp key="timestamp" datePattern="yyyyMMdd_HHmmss"/>
<springProperty name="AWS_ACCESS_KEY" source="cloud.aws.credentials.access-key"/>
<springProperty name="AWS_SECRET_KEY" source="cloud.aws.credentials.secret-key"/>
<springProperty name="PROFILE" source="spring.profiles.active"/>

 

라이브러리의 핵심은 Appender 구성입니다.

다음은 Logback의 Appender 인터페이스 클래스 다이어그램입니다. 로깅 타입에 따라 콘솔 또는 파일 출력을 선택해서 Appender 구현체 클래 스를 지정합니다. logback-awslogs-appender 라이브러리의AwsLogsAppender 클래스는 UnsynchronizedAppenderBase<ILoggingEvent> 상속 구현 합니다.

 

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
    </appender>
    <appender name="ASYNC_AWS_LOGS" class="ca.pjer.logback.AwsLogsAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <layout>
            <pattern>[%thread] [%date] [%level] [%file:%line] - %msg%n</pattern>
        </layout>
        <logGroupName>chime-app-log/${PROFILE}</logGroupName>
        <logStreamUuidPrefix>log-${timestamp}-</logStreamUuidPrefix>
        <logRegion>ap-northeast-2</logRegion>
        <maxBatchLogEvents>50</maxBatchLogEvents>
        <maxFlushTimeMillis>30000</maxFlushTimeMillis>
        <maxBlockTimeMillis>5000</maxBlockTimeMillis>
        <retentionTimeDays>0</retentionTimeDays>
        <accessKeyId>${AWS_ACCESS_KEY}</accessKeyId>
        <secretAccessKey>${AWS_SECRET_KEY}</secretAccessKey>
</appender>

 

콘솔 로그 CONSOLE와 AWS CloudWatch 로그 ASYNC_AWS_LOGS 출력 두가지 appender를 설정하였습니다. ASYNC_AWS_LOGS 는 로그 레벨 중 ERROR로그 이상만 출력하도록 필터링 합니다.

 

AWSLogsAppender 주요 태그

logGroupName: CloudWatch log Group Name. 개발서버와 운영서버 로그 분리를 위해 active profile 에 따라 다른 그룹을 지정하여 구분해 놓았습니다.
logStreamUuidPrefix: 로그 스트림 UUID 앞에 prefix를 지정할 수 있습니다. 타임스탬프를 넣어주었습니다.
logRegion: CloudWatch AWS Region maxBatchLogEvents: 배치의 최대 이벤트 갯수를 설정하는 것이며 1 ~ 10000사이 값만 설정이 가능. 이벤트 대기열에 갯수가 50개가 되면 AWS Cloud Watch 로 로그가 전송됩니다.
maxFlushTimeMillis: 마지막 플러시가 발생된 이후 지정된 시간이 지나면 AWS Cloud Watch로 로그가 전송. 0일 경우 로그를 동기로 전송하고 0보다 큰값일 경우 비동기로 로그가 전송됩니다. 라이브러리는 단일 스레드 방식으로 flushTime을 작동되며 맨 첫번째 생성된 로그는 바로 flush된 이 후 스케줄러처럼 일정한 시간을 지켜 flush 합니다.
maxBlockTimeMillis: 로그가 전송되는 동안 코드가 계속 실행되는 것을 차단하고 값을 0으로 세팅하면 전송중에 발생되는 모든 로그를 버립니다.
retentionTimeDays: 로그그룹의 보존기간을 얘기합니다. 0으로 세팅하면 보존기간은 무기한으로 보존됩니다.

콘솔 로그를 default 로 사용하고 active profile이 dev, prod일때만 CloudWatch 로그 스트림을 생성합니다.

<springProfile name="dev,prod">
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_AWS_LOGS"/>
    </root>
</springProfile>
<springProfile name="default">
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</springProfile>

 

전체 코드는 다음과 같습니다.

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <property name="LOG_PATTERN"
              value="${LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){blue} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <timestamp key="timestamp" datePattern="yyyyMMdd_HHmmss"/>
    <springProperty name="AWS_ACCESS_KEY" source="cloud.aws.credentials.access-key"/>
    <springProperty name="AWS_SECRET_KEY" source="cloud.aws.credentials.secret-key"/>
    <springProperty name="PROFILE" source="spring.profiles.active"/>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
    </appender>
<appender name="ASYNC_AWS_LOGS" class="ca.pjer.logback.AwsLogsAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <layout>
            <pattern>[%thread] [%date] [%level] [%file:%line] - %msg%n</pattern>
        </layout>
        <logGroupName>chime-app-log/${PROFILE}</logGroupName>
        <logStreamUuidPrefix>log-${timestamp}-</logStreamUuidPrefix>
        <logRegion>ap-northeast-2</logRegion>
        <maxBatchLogEvents>50</maxBatchLogEvents>
        <maxFlushTimeMillis>30000</maxFlushTimeMillis>
        <maxBlockTimeMillis>5000</maxBlockTimeMillis>
        <retentionTimeDays>0</retentionTimeDays>
        <accessKeyId>${AWS_ACCESS_KEY}</accessKeyId>
        <secretAccessKey>${AWS_SECRET_KEY}</secretAccessKey>
</appender>
    <springProfile name="dev,prod">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="ASYNC_AWS_LOGS"/>
        </root>
    </springProfile>
    <springProfile name="default">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>
</configuration>

 

테스트 하기

사용자 컨트롤러에서 에러 로그를 추가해 보았습니다.

log.error("error log test");

 

CloudWatch의 로그 그룹이 잘 생성되었는지 확인해봅니다.