본문 바로가기
프로젝트 관련

Spring boot - 로그백(Logback)을 통한 로그 파일 관리

by khds 2024. 10. 8.

 

들어가기 

 

프로젝트를 진행 중, 실제 사용자로 인해 발생하는 예외 및 로그를 확실히 파악하기 위해서 로그를 기록한 파일을 정리하고자 하였다. 

이를 위해 스프링에서 사용할 수 있는 로깅 프레임워크 중 하나인 Logback을 통해 진행하고자 한다. Logback은 SLF4J의 구현체이며 Spring Boot 환경이라면 별도의 dependency 추가 없이 기본적으로 포함되어 있어(spring-boot-starter-web), 간편하게 로그 처리를 진행할 수 있다. 

아래와 같이 @Slf4j 어노테이션을 지정해 주는 것으로 해당 클래스 내에서 사용할 수 있으며, 로그 레벨(trace, debug, info, warn, error)에 따라 메서드를 사용하고 메시지를 남기면 쉽게 사용할 수 있다. 

@Slf4j
@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        log.trace("test success");
        log.debug("test success");
        log.info("test success");
        log.warn("test success");
        log.error("test success");
        return "success";
    }
}

 

하지만 위 코드의 실행 결과는 아래와 같다.

 

log.trace(), log.debug()를 통해 출력한 결과가 콘솔에 표시되지 않는다..!

이 이유는 콘솔 출력시 INFO 이상의 레벨만 출력되는 것이 기본으로 설정되었기 때문이다. 

만약 설정을 바꾸고 싶다면 추가적인 작업을 해야 한다. 

 

이 글에서는 Logback을 통해 로그 처리 및 출력 방식을 어떻게 설정할 수 있는지 xml 파일을 통해 다뤄볼 것이다. (config 클래스를 생성하거나 application.properties 내 간단히 구성하는 방법도 존재하지만, 이 글에서는 xml 파일을 통해 작성하는 방법만 설명한다.)

Spring boot 버전은 3.2.5 이다.

 

 

본론

1. 로깅 및 로그 레벨

 

로깅이란 서비스를 개발하거나 운영에 필요하며 이벤트나 메시지 내 필요한 정보를 출력 매체를 통해 보여주거나 기록하여 이를 통해 서비스를 진단, 분석할 수 있게 하는 활동을 의미한다. 서비스 시 발생하는 에러를 파악하여 추적하며 디버깅할 수 있고, 이벤트 발생 및 중요 정보를 모니터링할 수 있다는 점에서 중요성을 가진다.

Spring에서는 Logback를 주로 사용하며 로그 메시지별 레벨을 설정하여 심각도를 나타낼 수 있다.

 

5가지 레벨이 존재하는데, TRACE < DEBUG < INFO < WARN < ERROR 순으로 심각도를 나타낸다.

1. TRACE: 프로그램의 매우 상세한 실행 흐름을 기록하는 데 사용한다. 일반적으로 디버깅이나 매우 구체적인 문제를 추적할 때 유용하다.

2. DEBUG: 디버깅 목적으로 프로그램의 흐름을 추적할 때 사용. 일반적으로 개발 중에 사용되며, 문제 해결이나 코드 분석에 도움을 준다. 시스템의 상태나 중요한 변수의 값을 확인하는 데 유용하다.

3. INFO: 애플리케이션의 정상적인 운영을 나타내는 일반적인 정보 로그. 주로 시스템이 제대로 작동하고 있음을 확인하는 데 사용되며, 예외적이지 않은 상황에서 발생하는 주요 이벤트를 기록한다.

4. WARN: 경고성 메시지를 나타낸다. 심각한 오류는 아니지만, 주의해야 할 상황을 기록하는 로그이다. 

5. ERROR: 예상치 못한 심각한 오류나 예외 상황을 기록한다. 이 레벨의 로그는 빠르게 해결해야 할 문제를 의미한다.

참고로 TRACE와 DEBUG는 로그의 양이 많아 성능에 영향을 줄 수 있으므로, 평상시에는 사용하지 않는다.

 

 

2. logback-spring.xml 파일을 통한 로그 처리

 

로그 기록 방법에는 콘솔, 파일, 이메일, 데이터베이스 등이 있으며 가장 자주 보는 화면은 콘솔 화면으로 아래와 같은 화면을 자주 보았을 것이다.

 

하지만 서버를 다시 실행하면, 콘솔에 기록한 로그는 지워지기에, 이를 파일로 기록을 해보려고 한다. 

이때 logback-spring.xml 파일을 통해 구체적으로 지정할 수 있는데, Logback을 사용하여 로그 처리를 할 때 아래 4가지 장점을 갖는다.

1. 서비스나 앱의 운영 환경(local, dev 등)에 따라 별도로 로그 설정 적용 가능

2. log4j보다 뛰어난 성능, 필터링, 로그레벨 변경 등의 향상

3. 압축 지원, 보관 기간 관리 가능

4. 스프링 프로파일(출력 방식)에 따라 다른 로그 설정이 적용

 

우선 logback.spring.xml 파일을 아래와 같이 resources 폴더 내에 파일을 작성한다.

 

이제 logback-spring.xml 파일 내부를 살펴보며 어떻게 로그 처리 방식을 구성할 수 있는지 확인해 보자. 

아래의 코드는 프로젝트에서 진행 중인 logback-spring.xml 내부의 전체적인 구조를 간략하게 표현하였다.(appender 내부 코드는 이후 설명) 

<?xml version="1.0" encoding="UTF-8"?>
<!-- 60초마다 설정 파일의 변경을 확인하고 변경 시 갱신 -->
<configuration scan="true" scanPeriod="60 seconds">

  <!--내부에서 사용할 값들을 개별적으로 지정할 수도 있다.-->
  <!-- log level -->
  <springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/>
  <!-- log file path -->
  <property name="LOG_PATH" value="log"/>
  <!-- log file name -->
  <property name="LOG_FILE_NAME" value="testLog"/>
  <!-- err log file name -->
  <property name="ERR_LOG_FILE_NAME" value="err_log"/>
  <!-- pattern -->
  <property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>

  <!-- 콘솔 출력 -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 아래서 설명 -->
  </appender>

  <!-- 파일 출력 -->
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 아래서 설명  -->
  </appender>

  <!-- 에러 파일 출력 -->
  <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 아래서 설명 -->
  </appender>

  <!-- appender 적용 -->
  <root level="${LOG_LEVEL}">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="Error"/>
  </root>

  <!-- 특정패키지 로깅레벨 설정 -->
  <logger name="com.example.test.testController" level="DEBUG" additive="false" >
	<appender-ref ref="CONSOLE"/>
  </logger>
</configuration>

 

위 파일에서 간단하게 다음과 같은 태그들을 확인할 수 있다.

 

1. configuration

configuration는 일반적인 config 문서와 같이 시작과 끝에서 전체적인 설정을 감싸주며, scan="true"를 통해 설정 파일이 변경되는지 주기적으로 확인하도록 해준다. 이는 logback-spring.xml 파일을 수정 후 서버를 재실행할 필요가 없다는 의미고, scanPeriod 옵션을 통해 파일의 변경사항을 확인하는 주기를 설정할 수 있다. 이외에도 debug, packaginData 등 다양한 속성이 있으며 굳이 지정하지 않아도 상관없다.

 

2. springProperty

해당 태그는 외부에서 지정한 값을 변수로서 가져올 수 있는 기능으로 위 코드에서는 name이 LOG_LEVEL이라는 이름으로  source인 logging.lever.root라는 곳에서 가져온다는 의미이다. scope가 context로 설정되어 있는데 이는, 스프링에 applicationContext에서 값을 검색하여 가져온다는 의미이다. 즉, Spring의 ApplicationContext에 등록할 속성을 정의하는 application.properties(혹은. yml) 파일 내 정의된 변수를 가져오는 것이다. 

logging.level.root = info

 

context 외 다른 속성으로는 system 속성이 있고, Java의 시스템 속성은 JVM이 시작될 때 설정되며, -D 옵션을 사용하여 설정할 수 있다.

java -D my.property=value -jar myapp.jar

 

3. property

해당 태그는 springProperty와 비슷한데, 파일 내부에서 사용할 값을 name과 value로 지정하여 사용한다. 위 코드에선 로그 파일을 저장할 위치를 지정하기 위한 값으로  LOG_PATH와 FILE_NAME를 지정하였다.  눈여겨볼 점은 로그 출력 패턴을 미리 지정한 점이다.(springProperty와 마찬가지로 미리 지정하지 않아도 상관없다.)

<property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>

 

LOG_PATTERN이란 이름으로 로그 패턴을 미리 지정해 놓았는데, 해석하면 아래와 같다.

%-5level: 로그 레벨을 출력한다. -5는 로그 레벨을 5자리로 표시하며, 부족한 공간은 공백으로 채워진다.
%d{yy-MM-dd HH:mm:ss}: 로그 이벤트가 발생한 날짜와 시간을 출력한다.
[%thread]: 로그 이벤트를 생성한 스레드의 이름을 출력한다.
[%logger{0}:%line]: 로그 이벤트를 생성한 메서드와 소스 코드의 줄 번호를 출력한다. {0}는 마지막 요소(경로 x, 메서드 이름만)만 출력하도록 지정한다.
%msg: 로그 메시지를 출력한다.
%n: 줄 바꿈 문자를 출력한다.


4. root

root 태그는 작성한 appender를 적용하는 역할을 하며, root 태그 아래 작성하지 않는다면 appender는 동작하지 않는다.(level은 굳이 적용시켜주지 않아도 상관없다.)

 

5. logger

logger 태그는 특정 패키지를 기준으로 로그 기준을 적용할 수 있는 태그이다. appender-ref 태그에 해당하는 appender에 정의한 로그 규정에 따라 처리한다.

 

6. appender

appender 태그는 logback 파일의 핵심으로 로그를 어디에, 어떻게 출력할지를 결정하는 태그이다. name 속성과 class 속성을 넣을 수 있는데, name 속성으로 이름을 지정하고 class 속성은 Appender의 구현 클래스를 지정한다. 위 코드에서 사용한 클래스는 두 가지인데, "ch.qos.logback.core.ConsoleAppender"는 로그 메시지를 콘솔에 출력하고, "ch.qos.logback.core.rolling.RollingFileAppender"는 로그 메시지를 파일에 출력하며, 파일에 크기가 일정 크기에 도달하면 새 파일을 생성한다. 

즉, 위 코드에선 appender로 원하는 출력 방식을 지정하고, 출력 방식별 다른 기준을 적용할 수 있는 것이다. 나는 CONSOLE, FILE, ERROR 3가지로 구분하였다. FILE과 ERROR는 같은 class를 적용하였는데, 파일 이름을 다르게 하여 ERROR 파일은 로그 레벨이 ERROR인 로그만 출력하도록 설정할 것이다. 

참고로 class는 콘솔, 파일 외 다양한 구현 클래스를 지정할 수 있으며 아래와 같다. (외부 라이브러리를 추가하여 적용할 수도 있다. ex. AWS CloudWatch는 ca.pjer.logback.AwsLogsAppender)

1. ch.qos.logback.core.ConsoleAppender: 로그 메시지를 콘솔에 출력
2. ch.qos.logback.core.FileAppender: 로그 메시지를 파일에 출력
3. ch.qos.logback.core.rolling.RollingFileAppender: 로그 메시지를 파일에 출력하며, 파일의 크기가 일정 크기에 도달하면 새 파일을 생성
4. ch.qos.logback.core.rolling.SiftingAppender: 로그 메시지를 다양한 대상에 동적으로 출력, 이 Appender는 MDC (Mapped Diagnostic Context) 값을 기반으로 로그 메시지를 분류하고, 각 분류에 대해 별도의 Appender를 생성
5. ch.qos.logback.classic.net.SMTPAppender: 로그 메시지를 이메일로 전송
6. ch.qos.logback.classic.net.SocketAppender: 로그 메시지를 네트워크 소켓을 통해 전송
7. ch.qos.logback.classic.net.SyslogAppender: 로그 메시지를 Syslog 서버로 전송

 

그렇다면 appender 태그 내부는 어떻게 작성할 수 있을까? 위에서 작성한 CONSOLE, FILE, ERROR appender를 아래와 같이 작성하였다. 

 

CONSOLE

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

 

우선 콘솔 출력 설정은 <encoder> 태그 내부에 <pattern> 태그를 작성하였다. "ch.qos.logback.classic.encoder.PatternLayoutEncoder"는 패턴을 사용하여 로그 메시지의 형식을 지정해 주는 클래스로 pattern 내부에 미리 지정해 둔 패턴 값을 지정하였다.

 

FILE

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <!-- 파일경로 설정 -->
  <file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
  <!-- 출력패턴 설정-->
  <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>${LOG_PATTERN}</pattern>
  </encoder>
  <!-- Rolling 정책 -->
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- .gz,.zip 등을 넣으면 자동 일자별 로그파일 압축 -->
    <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
      <!-- 파일당 최고 용량 kb, mb, gb -->
      <maxFileSize>10MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거-->
    <maxHistory>3</maxHistory>
  </rollingPolicy>
</appender>

 

파일 출력 설정에서 확인할 점은 다음과 같다.

<file> : 로그 메시지가 저장될 경로와 파일 이름을 지정한다. 

<rollingPolicy> : 로그 파일의 롤링 정책을 지정한다. 롤링 정책은 로그 파일이 일정 크기에 도달하거나 일정 시간이 경과했을 때 새로운 로그 파일을 생성하는 방법을 정의하며, 'TimeBasedRollingPolicy'는 일정 시간 간격으로 파일이 생성된다.(SizeBasedTriggeringPolicy는 크기 기반)

<fileNamePattern> : 로그 파일의 이름 패턴을 지정한다. 

<timeBasedFileNamingAndTriggeringPolicy> : 시간 기반의 파일이름, 트리거를 지정하는 정책이다. 'SizeAndTimeBasedFNATP'와  <maxFileSize>를 통해 파일 당 최대 용량을 지정할 수 있다. 위 코드에서는 날짜별로 파일을 구분하고, 날짜 내 파일 하나에 크기가 지정한 용량을 넘어설 경우 동일 날짜의 파일을 추가로 생성하도록 한다. 만약 이 태그를 지우면 하나의 날짜에 하나의 파일에 모든 로그가 담길 것이다. 

<maxHistory>: 파일이 보관될 최대 보관 주기를 지정한다.

 

ERROR 

<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_LOG_FILE_NAME}.log</file>
  <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>${LOG_PATTERN}</pattern>
  </encoder>
  <!-- Rolling 정책 -->
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- .gz,.zip 등을 넣으면 자동 일자별 로그파일 압축 -->
    <fileNamePattern>${LOG_PATH}/${ERR_LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
      <!-- 파일당 최고 용량 kb, mb, gb -->
      <maxFileSize>10MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거-->
    <maxHistory>3</maxHistory>
  </rollingPolicy>
</appender>

 

에러 파일 출력에서 추가된 부분은 아래와 같다. 

<filter class="ch.qos.logback.classic.filter.LevelFilter">
  <level>error</level>
  <onMatch>ACCEPT</onMatch>
  <onMismatch>DENY</onMismatch>
</filter>

 

filter 태그는 특정 부분의 로그만 출력하는 것으로 'LevelFilter' 구현체를 통해 로그 레벨을 기준으로 필터링하도록 하였다. level을 error로 지정하여 ERROR 레벨의 로그만 수집하는 것을 알 수 있다.

<onMatch>: 필터링 조건과 일치하는 로그 이벤트에 대한 동작을 지정. ACCEPT는 해당 로그 이벤트를 수락하라는 의미이다.
<onMismatch>: 필터링 조건과 일치하지 않는 로그 이벤트에 대한 동작을 지정. DENY는 해당 로그 이벤트를 거부하라는 의미이다.

 

 

3. local 서버와 dev 서버 다르게 로그를 적용하기

 

로컬 서버하고 운영 서버를 따로 운영한다면, 서버에 따라 로그 처리 기준을 다르게 하고 싶을 수 있다. 그럴 경우 사용할 수 있는 것이 'springProfile' 태그이다. 

springProfile 태그에 name에 서버 profile를 지정한 후 root 태그 내 적용할 appender를 선택하여 적용할 수 있다. 아래의 코드를 보자.

<configuration>
  <springProfile name="dev">
    <!-- dev 프로파일에 대한 설정 -->
    ...
    <root>
      <appender-ref ref="CONSOLE"/>
      <appender-ref ref="FILE"/>
    </root>
  </springProfile>
  <springProfile name="local">
    <!-- local 프로파일에 대한 설정 -->
    ...
    <root>
      <appender-ref ref="CONSOLE"/>
    </root>
  </springProfile>
  <appender name="CONSOLE">
    <!-- CONSOLE Appender 설정 -->
    ...
  </appender> 
  <appender name="FILE">
    <!-- FILE Appender 설정 -->
    ...
  </appender>
</configuration>

 

local, dev 두 개의 springProfile 태그로 구분하여 appender을 선택적으로 적용한 것을 확인할 수 있다.

 

 

결론

 

이렇게 간단하게 Logback를 활용하여 로그 처리 방식을 지정할 수 있었다. 서비스에서 로그를 기록하는 것은 디버깅 및 정보를 조회할 때 반드시 필요한 행위라고 생각하기에, 이렇게 정리한 글이 많은 도움이 되었으면 좋겠다. 그리고 태그별 사용하지 않은 class가 많은데, 문서를 확인하며 선택적으로 class를 적용하면 좋을 것이다.

하지만 단순히 서버에만 저장하는 것은 서버 내부 용량을 차지하는 것과 로그를 확인하기 위해 서버 내부의 파일에 접근해야 한다는 불편함이 있다. 또한, 검색 기준에 따라 로그를 조회할 때, 파일은 txt(log) 형태로 존재하기에 원하는 정보를 바로바로 확인하기는 어려울 것이다.  서버 내부에 로그 파일을 저장하는 것보다 다른 곳으로 전송하는 것이 효율적이며, ELK EFK라는 것이 흔히 들어볼 수 있는 도구이다. 

ELK는 Elasticsearch, Logstash, Kibana, EFK는 Elasticsearch, Fluentd, Kibana를 나타내는데 3가지 도구를 활용한 만큼 로그를 효율적으로 저장하고 조회할 수 있겠지만, 작은 서비스에서는 위 도구를 상시 운용하기에는 비용적으로 부담이 있을 것이다. 

그런 점에서 AWS EC2를 사용하고 있다면, AWS CloudWatch는 어떨까? 서버를 따로 설치, 운용할 필요 없이 AWS에서 제공해 주는 기능으로 간편한 설정으로 쉽게 로그를 수집하고, 조회할 수 있다.

다음 글에서는 Logback을 AWS CloudWatch와 연계하여 로그를 수집하는 과정을 확인해 보겠다. 

 

 

참고

 

https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial/

 

Logback 으로 쉽고 편리하게 로그 관리를 해볼까요? ⚙️

Spring Boot…

tecoble.techcourse.co.kr

 

https://breakcoding.tistory.com/400

 

[Spring] logback 파헤치기 (로그 레벨 설정, 프로필별 로그 설정, 글자 색상 변경)

안녕하세요 오늘은 logback을 통해 로그를 관리하는 방법을 알아보겠습니다. 목차 - 로그 색상 바꾸기 - 프로필에 따라 로그 레벨 다르게 설정하기 - 로그 파일을 분할해서 저장하기 - JPA SQL을 로그

breakcoding.tistory.com

 

https://awse2050.tistory.com/72

 

Spring Boot Logback 설정으로 로그파일 생성

최근 회사에서 예외처리(에러) 및 엔드포인트에 대한 응답에 대한 데이터를 확인하기 위해서 직접 Slf4j 를 통해서 작성을 했으나 현재 동작시키고 있는 리눅스에서 백그라운드로 실행한다지만

awse2050.tistory.com