[스프링/Spring] slf4j, log4j2 로그 설정 하는 방법

개발을 하게되면 특별한 경우를 제외하고 남이 짜놓은 코드를 리팩터링하거나 기능을 추가하는 식으로 진행됩니다. 그래서 프로젝트를 처음 시작하면서 생각해야할 것들을 제대로 공부하지 못하는 경우가 많은 것 같습니다.

이번 글에서는 가장 기본적이면서도 중요한 요소인 로깅에 대해서 정리할 필요성을 느끼게 되었습니다.

로그란?

로그는 간단하게 말해서 연속된 데이터의 기록이라고 할 수 있습니다.

일반적으로 처음 프로그래밍을 배울 때는 보통 System.out.print 같은 언어에서 제공해주는 메소드를 사용해보셨을 것입니다. 프로그램이 실행되면서 콘솔에 무엇인가가 출력되는데, 이런 것들이 로그가 될 수 있습니다.

Logging Framework

System.out.print를 사용하여 디버깅을 할 수 있지만, 만약 어플리케이션의 사이즈가 커지게 되면, 이런 방식이 얼마나 비효율적인지를 느끼실 수 있을 것입니다.

Logging Framework가 없다면?

public void test() {
  System.out.println("test() 메소드 실행");
  try {
    someMethod();
  } catch (Exception e) {
    System.out.println(e.message);
    e.printStackTrace()
  }
}

위 코드는 간단하게 메서드에서 println를 이용하여 메소드 내부의 로깅을 하는 예제입니다.

어플리케이션의 사이즈가 커지면 어떻게 될까요?

위와 같은 로깅 코드를 가지고 있는 메소드들이 많아질 것이고, 로깅을 위해 시간, 패키지의 위치, 메소드 명 등의 내용까지 붙여지게되면 코드량도 많아지고 관리하기도 어려워질 것 입니다.

이 어플리케이션을 필요로하는 사람에게 판매를 하게된다면 어떨까요?

소비자에게는 어플리케이션의 콘솔창으로부터 로그 메시지가 발생하는 것을 원치 않는 상황이 생길 것입니다. 만약 이 어플리케이션에 문제가 생겨 디버깅을 해야하는 상황이 생기면 우리는 또 다시 System.out.print를 사용할 수 밖에 없는 상황에 놓이게 됩니다.

그 외에도 한 가지 생각해봐야 하는 것이 있습니다.

필요할 수도 있는 추적용 데이터는 어떻게 저장할까요?

예를 들어 IP 주소, 디바이스 버전과 같은 정보들은 나중에 데이터 추적을 하거나 디버깅을 위해서 로그를 남길 필요가 생길 수 있습니다.

System.out.print를 통해서 남겨진 결과는 콘솔에 남지만, 콘솔의 메모리에 언제까지고 저장하고 있을 수 없어 위와 같은 정보들을 파일에 저장할 필요가 생기게 됩니다.

  • 비즈니스로직에서 로그성 코드를 분리
  • 상황에 따라 유연하게 대처할 수 있도록 로그를 레벨로 분리하여 노출 및 관리
  • 특정한 로그는 정해진 파일에 저장

간단하게 정리하면 위와 같은 요구사항이 생겨, Logging Framework가 등장하게 되었습니다.

Slf4j

스프링의 Logging Framework에서 가장 유명한 라이브러리가 바로 slf4j(Simple Logging Facade For Java) 입니다.

slf4j는 다양한 자바 로깅 시스템을 사용할 수 있도록 해주는 파사드 패턴의 인터페이스라고 생각하시면 될 것 같습니다.

slf4j

위 그림과 같이 slf4j를 사용하면 logback, log4j, log4j2와 같은 구현체를 어플리케이션에 영향 없이 손쉽게 교체할 수 있습니다.

스프링 로그 설정 예제

지금부터는 스프링 프로젝트를 하나 만들면서 로그 설정하는 방법을 자세하게 다뤄보도록 하겠습니다.

  1. OS: MacOS
  2. IDE: Intellij
  3. Project: Spring initializr

의존성 추가

의존성의 경우, 기본적으로 spring-boot-starter-web과 개발 편의를 위한 lombok을 추가해주었습니다.

spring-boot-starter-web에는 기본적으로 logback 로깅 모듈이 포함되어 있지만, 성능 상 log4j2logback 보다 우수하기 때문에 이를 제외하고 log4j2를 추가해주는 작업을 해줄 것입니다.

// ... 생략

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
+   implementation 'org.springframework.boot:spring-boot-starter-log4j2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

+ configurations {
+     all {
+       exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
+     }
+ }

// ... 생략

위 코드처럼 log4j2 의존성을 추가해주고, spring-boot-starter-logging을 제외시켜 주었습니다.

log4j2 설정

log4j2를 설정하는 방식에는 다양한 방법들이 있지만, xml 설정방식을 제외한 나머지 설정에는 Jackson 등의 추가적인 라이브러리가 필요한 경우가 있습니다.

이 예제에서는 xml 설정 방식으로 진행하겠습니다.

패키지 구조

main
├── java
│   └── com
│       └── deeplify
│           └── tutorial
│               └── logging
│                   └── LoggingApplication.java
└── resources
    ├── application.yml
+   ├── log4j2.xml    // 설정 파일 추가
    ├── static
    └── templates
로그 설정 파일의 이름은 의존성에 따라 "logback.xml", "log4j.xml", "log4j2.xml"로 생성해야합니다.

간단한 콘솔 로그 설정

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="INFO">
    <Properties>
        <Property name="LOG_PATTERN">%d{HH:mm:ss.SSSZ} [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="ConsoleLog" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleLog" />
            <AppenderRef ref="FileLog" />
        </Root>
    </Loggers>
</Configuration>

하나 하나씩 무엇을 의미하는지 살펴보도록 하겠습니다.

  • Configuration: 로그 설정을 위한 최상위 요소
    • status 속성: Log4j2 내부의 동작에 대한 로깅 레벨을 설정 (log4j 내부 문제를 해결하기 위한 용도의 로깅이 필요한 경우 사용)
  • Properties: 하단 설정에 사용할 변수들을 정의
    • name: 위 예제에서 name=”LOG_PATTERN”으로 설정하여 LOG_PATTERN이라는 변수를 정의
  • Appenders: 로그가 출력되는 위치
    • Console: 콘솔에 출력될 로그 설정
      • name: 어펜더의 이름
      • target: 로그 타겟 (default: SYSTEM_OUT)
    • PatternLayout: 로그의 패턴을 설정
  • Loggers: 로깅 작업의 주체로 각 패키지 별로 다양한 설정을 할 수 있음
    • Root: 모든 패키지에 대한 로깅을 하기 위한 일반적인 로그 정책 설정 (한 개만 설정할 수 있음)
      • AppenderRef: 상단에 설정한 Appender를 참조
출력 예제
@Slf4j
@SpringBootApplication
public class LoggingApplication {

    public static void main(String[] args) {
        SpringApplication.run(LoggingApplication.class, args);
        log.trace("Hi I'm {} log", "TRACE");
        log.debug("Hi I'm {} log", "DEBUG");
        log.info("Hi I'm {} log", "INFO");
        log.warn("Hi I'm {} log", "WARN");
        log.error("Hi I'm {} log", "ERROR");
    }

}

설정을 해준 상태로 위와 같이 LoggingApplication.java 위치에 로그를 찍어보기 위해 코드를 작성하고, 실행해보았습니다.

20:22:20.831 [main] INFO  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm INFO log
20:22:20.831 [main] INFO  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm INFO log
20:22:20.832 [main] WARN  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm WARN log
20:22:20.832 [main] WARN  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm WARN log
20:22:20.832 [main] ERROR com.deeplify.tutorial.logging.LoggingApplication - Hi I'm ERROR log
20:22:20.832 [main] ERROR com.deeplify.tutorial.logging.LoggingApplication - Hi I'm ERROR log
레벨에 따른 출력
TRACE > DEBUG > INFO > WARN > ERROR

일반적으로 레벨을 위와 같이 구성되어 있습니다. 오른쪽으로 갈수록 꼭 출력해야 하는 중요한 레벨이라고 생각하시면 될 것 같습니다.

운영환경에서는 특별한 경우를 제외하고 INFO로 설정하는 경우가 많고, 개발 환경에서는 TRACE 또는 DEBUG로 설정하여 좀 더 자세한 로그를 확인할 수 있도록 합니다.

로그가 파일로 출력되도록 하는 설정 예제

<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="INFO">
    <Properties>
        <Property name="LOG_PATTERN">%d{HH:mm:ss.SSSZ} [%t] %-5level %logger{36} - %msg%n</Property>
    </Properties>
    <Appenders>
        <Console name="ConsoleLog" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
        </Console>
       <RollingFile name="FileLog"
                    fileName="./logs/spring.log"
                    filePattern="./logs/spring-%d{yyyy-MM-dd}-%i.log">
           <PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8" />
           <Policies>
               <TimeBasedTriggeringPolicy interval="1" />
               <SizeBasedTriggeringPolicy size="10000KB" />
           </Policies>
           <DefaultRolloverStrategy max="20" fileIndex="min" />
       </RollingFile>
    </Appenders>
   <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleLog" />
           <AppenderRef ref="FileLog" />
        </Root>
    </Loggers>
</Configuration>
  • RollingFile: 조건에 따라 파일에 로그를 출력하도록 설정
    • name: 어펜더의 이름
    • fileName: 경로를 포함한 파일 이름
    • filePattern: 롤링 조건에 따른 경로를 포함한 파일 이름 패턴
    • Policies: 파일 롤링 정책
      • TimeBasedTriggeringPolicy: 1일 단위(interval=1)로 새로운 파일에 로그를 기록
      • SizeBasedTriggeringPolicy: 파일 사이즈를 기준으로 용량이 넘칠 경우 다음 파일을 생성하여 기록
      • DefaultRolloverStrategy: 파일 용량 초과 시 생성될 수 있는 파일의 최대 개수 설정

로그를 파일로 저장하면, 로그 파일의 양도 관리를 해주어야합니다. 위의 복잡해보일 수 있는 설정들은 로그파일의 최대 용량 설정날짜별로 로그파일을 관리하기 위한 것들입니다.

파일 출력 예제
  .
+ ├── logs
+ │   └── spring.log  // 로그파일 생성
  └── src
      ├── main
// ... 생략
20:25:20.831 [main] INFO  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm INFO log
20:25:20.832 [main] WARN  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm WARN log
20:25:20.832 [main] ERROR com.deeplify.tutorial.logging.LoggingApplication - Hi I'm ERROR log

어플리케이션을 실행하면 위와 같은 위치에 로그 폴더와 파일이 생기는 것을 확인하실 수 있고, (폴더는 제가 임의로 생성했습니다.) 파일 내부에 로그 기록들이 남아 있는 것도 확인하실 수 있습니다.

패키지별 로그 설정

<Loggers>
    <Root level="info">
        <AppenderRef ref="ConsoleLog" />
        <AppenderRef ref="FileLog" />
    </Root>
   <Logger name="com.deeplify.tutorial.logging" level="debug">
       <AppenderRef ref="ConsoleLog" />
   </Logger>
</Loggers>
  • Logger: 패키지 단위로 로그 설정
    • name: 이름에 패키지 경로를 설정
    • level: 해당 패키지의 로그 레벨 설정

위와 같은 방식으로 패키지 별로 로그를 관리할 수 있도록 하는 설정도 제공하고 있습니다. 자신의 어플리케이션에서 상황에 따라 적절하게 이용하면 좋을 것 같습니다.

패키지별 로그 예제
+ 20:26:20.731 [main] DEBUG com.deeplify.tutorial.logging.LoggingApplication - Hi I'm DEBUG log
20:26:20.731 [main] INFO  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm INFO log
20:26:20.732 [main] WARN  com.deeplify.tutorial.logging.LoggingApplication - Hi I'm WARN log
20:26:20.732 [main] ERROR com.deeplify.tutorial.logging.LoggingApplication - Hi I'm ERROR log

실행 후, 콘솔창의 결과를 확인해보면 위와 같이 DEBUG 레벨의 로그가 남겨져 있는 것을 확인할 수 있습니다.

예제에서 보여드린 코드는 위 프로젝트에서 확인하실 수 있습니다.

참고 문서

맺음

간단하게 스프링에서 logging Framework를 이용한 로깅 설정하는 방법에 대해서 알아보았습니다. 혹시 궁금하신 점이나 이상한 점이 있으면 댓글 부탁드리겠습니다.

감사합니다.

Buy me a coffee
글이 도움이 되셨다면, 커피 한 잔만 사주세요!
Comments
Copied to clipboard