본문 바로가기

Java and Spring

LogBack을 활용한 Log파일 관리

개인 프로젝트를 진행하면서 EC2서버에서 Nginx를 활용하여 무중단 CI /CD를 구축하였습니다.

하지만, 배포를 하는 과정에서 EC2서버에서 가끔씩 에러가 발생하여 서버가 죽는현상이 발생하였고, 이유를 알아내기위해 배포 Shell 스크립트도 많이 보았지만, 원인을 밝혀내지 못했습니다. 로컬환경에서는 콘솔창에 올라온 에러로그를 확인하여 해결하면 되지만, EC2에서는 로그를 보지못하여 LogBack을 프로젝트에 적용하여 로그 파일을 관리하게 되었습니다.

이번 포스팅에서는 LogBack의 사용방법과 실제 EC2환경에서의 로그파일이 어떻게 관리되는지 공부해보려고 합니다.

 

1. Logback

1 - 1. Logback이란

간단하게 자바에서 사용되는 로깅 라이브러리는 Log4j, Logback, Log4j2가 있는데, 개발된 순서도 Log4j, Logback, Log4j2순서로 개발되었습니다. Log4j는 2015년 8월 이후로 프로젝트가 종료되어서 Log4j2를 사용하라고 권장하고 있다고 합니다. 그 중에 Logback은 Log4j 뒤에나온 프레임워크로 가장 널리 사용되고 있는 프레임워크중 하나이며, Spring boot환경에서는 별도의 dependency추가 없이 기본적으로 포함되어 있다고 합니다.

 

1 - 2. Logback 프로젝트에 적용해보기

제가 진행하고 있는 개인 프로젝트는 아래와 같은 구조로 되어있는데, 무중단 배포를 위해 Nginx를 활용하여 Profile에 따라 8090, 8091포트로 프로젝트를 올려두었습니다.

 

간단하게 프로젝트에서 설정한 profile을 소개하면 아래와 같습니다.

  • 8080 포트(로컬) = 'default'
  • 8090 포트 = 'was1'
  • 8091 포트 = 'was2'

현재 토이 프로젝트에 적용된 logback-spring.xml 파일입니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <!-- 30초마다 로그에 변경이 있는지 체크하는것 -->

    <!-- springProfile 태그를 사용하여 profile 별 property 값 설정 -->
    <springProfile name="default">
        <!-- local log file path -->
        <property name="LOG_PATH" value="./logs"/>
    </springProfile>
    <springProfile name="!default">
        <!-- dev log file path -->
        <property name="LOG_PATH" value="/home/ec2-user/log"/>
    </springProfile>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- [시간][로그레벨][thread이름] logger이름 메시지 \n 의 형식으로 출력 -->
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/marcket-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>2</maxHistory>
            <totalSizeCap>15MB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/err_application.log</file>
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/marcket-error-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>2</maxHistory>
            <totalSizeCap>15MB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <root level="INFO">
        <!-- INFO단계 위로 모두 아래의 console 방식으로 출력하는것임 -->
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR"/>
    </root>
</configuration>

 

 

너무 길고 복잡해 보이지만 큰 단위별로 자세하게 설명드리도록 하겠습니다.

<!-- springProfile 태그를 사용하여 profile 별 property 값 설정 -->
<springProfile name="default">
    <!-- local log file path -->
    <property name="LOG_PATH" value="./logs"/>
</springProfile>
<springProfile name="!default">
    <!-- dev log file path -->
    <property name="LOG_PATH" value="/home/ec2-user/log"/>
</springProfile>

 

<property name="LOG_PATH" value="~">는 xml파일 내에서 LOG_PATH라는 변수로 사용되는 의미이며, 토이 프로젝트가 EC2에 올라가 있기때문에 Local환경 및 EC2환경에서의 로그파일이 저장될 경로를 profile별로 나누기 위해 위와 같이 작성하였습니다.

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <!-- [시간][로그레벨][thread이름] logger이름 메시지 \n 의 형식으로 출력 -->
        <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
    </encoder>
</appender>

콘솔에서 출력되는 로그 패턴을 지정하는 곳이며, 아래 문법과 같이 사용자가 커스텀하게 만들 수 있습니다.

  • %logger{length} : Logger name을 축약 할 수 있다. {length}는 최대 글자 수 ex)logger{35}
  • %-5level : 로그 레벨, -5는 출력의 고정폭 값(5글자)
  • %msg : - 로그 메세지(=%message)
  • ${PID:-} : 프로세스 아이디
  • %d : 로그 기록시간
  • %p : 로깅 레벨
  • %F : 로깅이 발생한 프로그램 파일명
  • %M : 로깅일 발생한 메소드의 명
  • %I : 로깅이 발생한 호출지의 정보
  • %L : 로깅이 발생한 호출지의 라인 수
  • %thread : 현재 Thread 명
  • %t : 로깅이 발생한 Threrad명
  • %c : 로깅이 발생한 카테고리
  • %C : 로깅이 발생한 클래스 명
  • %m : 로그 메세지
  • %n : 줄바꿈
  • %% : %를 출력
  • %r : 애플리케이션 시작 이후 부터 로깅이 발생한 시점 까지의 시간(ms)
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_PATH}/application.log</file>
    <encoder>
        <pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOG_PATH}/marcket-%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>2</maxHistory>
        <totalSizeCap>15MB</totalSizeCap>
    </rollingPolicy>
</appender>

<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>error</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <file>${LOG_PATH}/err_application.log</file>
    <encoder>
        <pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOG_PATH}/marcket-error-%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>2</maxHistory>
        <totalSizeCap>15MB</totalSizeCap>
    </rollingPolicy>
</appender>

 

 

옵션 설명
file 해당 경로에 파일을 생성
rollingPolicy class = "TimeBaseRollingPolicy" 일별 시간별로 롤오버하는 정책
fileNamePattern 자정이 되면 전날 기록된 로그파일을 우리가 지정한 패턴으로 지정된 경로에 저장
maxHistory 로그파일이 저장되는 기간
- fileNamePatter에 따라 저장되는 기간이 달라짐
- %d{yyyy-MM-dd}일 경우 옵션값을 2로 주면 2일동안 저장
- %d{yyyy-MM-dd_HH}일 경우 옵션값을 2로 주면 2시간동안 저장
totalSizeCap 전체용량
- maxHistory가 설정되었을 경우에만 동작
filter  지정한 로그레벨만 저장
- <onMatch>ACCEPT</onMatch> error레벨만 저장
- <onMisMatch>DENY</onMismatch> 나머지 레벨은 저장하지 않음

 

프로젝트에 적용된 logback-spring.xml파일을 자세하게 알아보았습니다.

실제로 EC2서버에 로그가 잘 쌓이고 있는지 확인해본 결과 원하는대로 로그가 잘 쌓이고 있음을 확인 할 수 있었습니다.

 

제가 LogBack을 공부하게 된 이유는 처음에 말했던것처럼 무중단 배포시 원인모르게 서버가 죽는 현상때문이였습니다. LogBack을 적용한 후에 에러로그 파일을 확인해보니 아래와 같이 로그가 적혀있었습니다.

org.springframework.boot.web.server.PortInUseException: Port 8090 is already in use

 

아래는 배포 파일의 일부입니다.

if [ -z $IDLE_PID ]
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $IDLE_PID"
  sudo kill -15 $IDLE_PID  // sudo 붙임
  sleep 10  // 5 -> 10으로 변경
fi

echo "> $IDLE_PROFILE 배포"
echo "> $IDLE_PROFILE "
nohup java -jar -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH > /dev/null 2>&1 &

 

배포 파일에서 해당 포트를 kill한 후에 nohup 명령어를 통해 프로젝트를 띄우는데, kill명령어가 안먹어서 그런건지 아니면 kill중에 nohup 명령어가 들어가서인지 해당 포트가 이미 사용되고 있다고 에러 로그에 적혀있어서 kill 명령어 앞에 sudo를 붙이고 sleep도 기존 5에서 10으로 변경하여 배포 파일을 수정하였습니다.