서버를 관리하게 되면 가장 많이 하는 일 중 하나가 로그를 읽는 작업이라고도 할 수 있습니다. 로그는 그 당시 어떤 일이 일어났는지 확인할 수 있게 해주는 중요한 단서이기 때문에 문제가 생겼을 때 해결하는데 많은 도움이 됩니다. 그렇기 때문에 이런 로그들은 반드시 기록하고 있어야 하며 일정 기간 동안 유실되지 않도록 잘 관리해야 합니다.
또한 모든 로그를 다 기록할 순 없기 때문에 필요한 로그들만 잘 기록해야 하고, 많은 로그들 중에서 손쉽게 필요한 로그들만 찾을 수 있도록 관리할 방법들도 필요한데요. AWS에서는 이러한 기능들을 편리하게 사용할 수 있도록 CloudWatch라는 서비스를 제공해주고 있습니다. CloudWatch Agent가 로그도 모니터링해서 CloudWatch logs로 전송하는 역할도 하고 있습니다. 그래서 이번 글에서는 CloudWatch와 Spring Logback을 사용해서 Spring Error log를 CloudWatch Log Group 으로 전송하는 법에 대해서 정리 해보겠습니다.
Spring 설정하기
Spring에서 CloudWatch로 Error log를 전송할 수 있도록 정말 편리하게 제공해주는 라이브러리 가 있습니다. 해당 라이브러리를 사용하려면 아래의 의존성을 추가해주어야 합니다.
저의 프로젝트 구조는 위와 같습니다. 가장 중요한 파일은 logback.xml 파일 인데요. resources 폴더 아래에 logback.xml 파일을 만드시면 됩니다.
logback.xml
<configuration packagingData="true">
<!-- Timestamp used into the Log Stream Name -->
<timestamp key="timestamp" datePattern="yyyy-MM-dd-HH-mm-ssSSS"/>
<!-- The actual AwsLogsAppender (asynchronous mode because of maxFlushTimeMillis > 0) -->
<appender name="COLOR" class="ca.pjer.logback.AwsLogsAppender">
<!-- Send only ERROR and above -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!-- Nice layout pattern -->
<layout>
<pattern>[%date] %highlight([%level]) [%logger{10} %file:%line] %msg%n</pattern>
</layout>
<!-- Hardcoded Log Group Name -->
<logGroupName>YAPP-log</logGroupName>
<!-- Log Stream Name UUID Prefix -->
<logStreamUuidPrefix>YAPP/</logStreamUuidPrefix>
<!-- AWS Region -->
<logRegion>ap-northeast-2</logRegion>
<!-- Maximum number of events in each batch (50 is the default) -->
<!-- will flush when the event queue has 50 elements, even if still in quiet time (see maxFlushTimeMillis) -->
<maxBatchLogEvents>50</maxBatchLogEvents>
<!-- Maximum quiet time in millisecond (0 is the default) -->
<!-- will flush when met, even if the batch size is not met (see maxBatchLogEvents) -->
<maxFlushTimeMillis>30000</maxFlushTimeMillis>
<!-- Maximum block time in millisecond (5000 is the default) -->
<!-- when > 0: this is the maximum time the logging thread will wait for the logger, -->
<!-- when == 0: the logging thread will never wait for the logger, discarding events while the queue is full -->
<maxBlockTimeMillis>5000</maxBlockTimeMillis>
<!-- Retention value for log groups, 0 for infinite see -->
<!-- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutRetentionPolicy.html for other -->
<!-- possible values -->
<retentionTimeDays>0</retentionTimeDays>
</appender>
<!-- A console output -->
<appender name="ASYNC_AWS_LOGS" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%date] %highlight([%level]) [%logger{10} %file:%line] %msg%n</pattern>
</encoder>
</appender>
<!-- Root with a threshold to INFO and above -->
<root level="INFO">
<appender-ref ref="COLOR"/>
<appender-ref ref="ASYNC_AWS_LOGS"/>
<if condition='isDefined("color")'>
<then>
<appender-ref ref="COLOR"/>
</then>
</if>
</root>
</configuration>
전체의 코드는 위와 같은데요. 이 중에서 자세히 볼 부분에 대해서만 정리를 해보겠습니다.
이번 글에서는 이정도의 설정만 보면 될 거 같고 자세히 더 알고 싶다면 여기 를 참고하시면 될 것 같습니다. 그리고 Spring logback 관련으로 찾아서 보아도 좋을 거 같습니다.
Controller 만들기
@Slf4j
@RestController
public class HelloController {
@GetMapping("/")
public String hello() {
log.error("에러입니다!");
return "hello";
}
}
저는 위와 같이 해당 API가 호출되면 error log를 출력하도록 만들어놓았습니다. 이게 설정 끝입니다! 정말 간단한 것 같습니다.(라이브러리의 힘...) (로컬에서 실행하고 위의 API를 호출해도 CloudWatch로 전송이 됩니다!)
하지만 이번 글에서는 EC2에 올렸을 때도 CloudWatch로 잘 전송되는지 확인해보기 위해서 프로젝트를 jar로 압축해서 EC2에 올린 후에 테스트를 해보겠습니다.
Spring jar 만들기
./gradlew clean build
그러면 위와 같이 jar 파일이 만들어지는데요. 저는 이것을 Filezila를 사용해서 EC2에 올리겠습니다. 간단하게 Filezila는 어떻게 사용하는지에 대해서도 정리해보겠습니다.
Filezila 사용법
위와 같이 하면 EC2로 jar 파일이 전송됩니다.
그리고 EC2에 접속해서 확인해보면 위와 같이 jar 파일이 존재하는 것을 확인할 수 있습니다. 바로 jar를 실행시키겠습니다.
sudo amazon-linux-extras install java-openjdk11 (제가 설치한 자바 11버전 명령어)
sudo nohup java -jar *.jar &
혹시 EC2에 자바가 설치되지 않았다면 java를 설치한 후에 위의 명령어를 사용하셔야 합니다.(저는 EC2 Linux2 버전입니다. java 8을 설치하고 싶다면 여기 를 참고하면 좋습니다.)
sudo netstat -tnlp
그러면 위의 명령어로 확인해보면 jar가 실행가 8080 포트에서 잘 실행되고 있는 것을 볼 수 있습니다.
그리고 해당 주소로 접속해보면 위와 같이 Controller에서 만든 대로 잘 응답이 오는 것도 확인할 수 있습니다. 위의 API 호출이 되었기 때문에 log.error()로 출력했던 로그가 CloudWatch로 전송이 되었을 것입니다.
정말 잘 되었는지 확인을 한번 해보겠습니다.
참고로 굳이 EC2에 올리지 않고 로컬에서 API 호출을 하여도 CloudWatch 로그 그룹으로 전송됩니다.
저는 위와 같이 CloudWatch에 Spring-log 라는 로그그룹이 존재하는데 여기로 log를 전송했습니다.
제가 몇번 호출했던 결과들이 CloudWatch 로그 그룹에 잘 출력이 되는 것을 확인할 수 있습니다.