프로그래밍/Java

[Java] SLF4J와 Logback을 이용한 로깅

일단개그하다 2022. 12. 24. 14:59

참고 링크

SLF4J
https://www.slf4j.org/
Logback
https://logback.qos.ch/
퍼사드 패턴
https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%82%AC%EB%93%9C_%ED%8C%A8%ED%84%B4
mvnrepository
https://mvnrepository.com/
예제 소스 코드
https://github.com/illdangag/logback-example/tree/main/01.logback-init

Logger

반려 동물과 함께 지내는 사람들이 반려 동물에게 한 가지 말만 가르 칠 수 있다면 가르치고 싶은 말이 "나 아파"라는 말이 있다

 

어플리케이션에서도 마찬가지로 어플리케이션의 현재 상태와 오류 상황 및 예상하지 못한 상황에서 어플리케이션이 개발자가 해당 상황을 빠르고 정확하게 파악할 수 있도록 기록을 얼마나 효율적으로 남기는 것이 매우 중요하다

 

자바의 표준 입출력 기능인 System.out을 사용하여 단순하게 로그를 남길 수 도 있지만 로그의 발생 위치와 그때 사용 중인 스레드, 그리고 콘솔 또는 파일로 로그를 출력 또는 둘 다 동시에 출력하는 기능 등으로 각 상황에 맞추어서 로그를 남길 수 있는 SLF4J와 Logback 라이브러리가 있다

SLF4J, Logback

SLF4J는 자바에서 로깅을 위한 인터페이스 라이브러리 중 하나로 이를 다양한 구현체들이 지원

SLF4J는 로깅을 위한 인터페이스의 묶음이며 퍼사드 패턴으로 이루어져 있으며 구현체를 포함하고 있지 않다

이에 대한 구현체로 Logback 라이브러리를 추가하여 로깅을 할 수 있다

프로젝트에 적용

프로젝트는 Gradle을 사용

예제 소스 코드

https://github.com/illdangag/logback-example/tree/main/01.logback-init

라이브러리 추가

dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.6'
    testImplementation 'ch.qos.logback:logback-classic:1.4.5'
}

https://mvnrepository.com/artifact/org.slf4j/slf4j-api

https://mvnrepository.com/artifact/ch.qos.logback/logback-classic

 

mvnreopsitory에서 slf4j-api, logback-classic을 검색하여 build.gradle의 dependencies에 추가

여기서 확인해 봐야 할 점이 logback-classic은 mvnrepository에서 testImplemenets로 추가하도록 하는데 라이브러리를 사용하고자 하는 프로젝트가 직접 실행되는 환경에서는 해당 라이브러리가 포함되지 않기에 아래 AppMain을 직접 실행시키는 경우에는 로그가 나타나지 않게 된다

 

프로젝트를 다른 어플리케이션에 라이브러리 형태로 제공하여 어플리케이션에서 사용하는 SLF4J의 구현체를 사용하도록 할 수 있다

프로젝트를 직접 실행시키는 경우에 로그를 출력하고 싶다면 testImplementation을 implementation으로 수정하면 된다

 logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" debug="false">
    <property name="LOGS_ABSOLUTE_PATH" value="./log"/> <!-- 로그 파일 위치 -->
    <property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS, GMT+9}][%thread][%-5level]\(%F:%L\): %msg%n"/> <!-- 로그 출력 패턴 -->

    <!-- 콘솔 출력 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern> <!-- 로그 출력 패턴 -->
        </encoder>
    </appender>

    <!-- 파일 출력 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- RollingFileAppender 사용 -->
        <file>${LOGS_ABSOLUTE_PATH}/application.log</file> <!-- 파일 경로 및 파일 이름 -->
        <encoder>
            <pattern>${LOG_PATTERN}</pattern> <!-- 로그 출력 패턴 -->
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 시간으로 파일 분할 정책 -->
            <fileNamePattern>${LOGS_ABSOLUTE_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 분할 파일 이름 패턴 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize> <!-- 분할 파일 크기 기준 -->
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>100</maxHistory> <!-- 분할된 파일의 최대 개수 -->
        </rollingPolicy>
    </appender>

    <!-- 로그 출력 기준 -->
    <root level="DEBUG">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

로그의 출력 패턴과 로그 출력 종류 그리고 파일로 저장하고 특정 기준에 맞추어서 파일 분할에 대한 설정 파일이다

 

로그 출력 패턴

configuration.appender.encoder.pattern에 설정하는데 위 설정 파일에서는 configuration.property에 설정
콘솔 출력과 파일 출력에 동시에 사용하도록 설정하였다.

콘솔 출력

configuration.appender에 이름을 CONSOLE로 설정하고 class는 ConsoleAppender를 사용한다

파일 출력

configuration.appender에 이름을 FILE로 설정하고 class는 RollingFileAppender 사용한다


file은 파일의 경로와 파일 이름

rollingPolicy에 파일 분할 정책을 설정하는데 TimeBasedRollingPolicy class를 설정

fileNamePattern로 분할된 파일의 경로와 이름을 설정하는데 파일 이름에 분할 시간을 설정하는데

위 설정에는 yyyy-MM-dd_HH-mm-ss와 같이 초 단위까지 설정하면 초 단위로 파일이 분할

일 단위로 분할하고 싶은 경우에는 yyyy-MM-dd 로 설정

시간 단위로 파일을 분할하더라도 하나의 파일의 크기가 너무 커지는 것을 막기 위해서 timeBasedFileNamingAndTriggeringPolicy에 SizeAndTimeBasedFNATP class를 설정하고 maxFileSize에 크기 설정하여 시간과 별개로 파일을 분할
maxHistory로 분할된 파일의 최대 개수를 설정

 

로그 출력 기준

configuration.root에 level 최소 로그 기준을 설정하고 출력할 appender를 추가

로깅 예제

package com.illdangag;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AppMain {
    private static final Logger log = LoggerFactory.getLogger(AppMain.class);

    public void run() throws Exception {
        log.info(" - AppMain.run() start");
        for (int index = 0; index < 100; index++) {
            log.debug("Logger Test: {}", index);
            Thread.sleep(10);
        }
        log.info(" - AppMain.run() end");
    }

    public static void main(String[] args) throws Exception {
        AppMain appMain = new AppMain();
        appMain.run();
    }
}

build.gradle을 예제와 같이 설정하였다면 AppMain 클래스의  main 메소드로 실행하게 되면 로그는 출력되지 않는다

이유는 SLF4J 인터페이스만 존재하고 구현체가 존재하지 않기 때문이다

package com.illdangag;

import org.junit.jupiter.api.Test;

public class AppMainTest {
    @Test
    public void runTest() throws Exception {
        AppMain appMain = new AppMain();
        appMain.run();
    }
}

테스트 코드로 실행하게 되면 로그가 정상적으로 출력 되게 된다