모르는게 많은 개발자

[스프링] 로깅 개념/설정 예제(feat. Logback) 본문

스프링

[스프링] 로깅 개념/설정 예제(feat. Logback)

Awdsd 2022. 3. 21. 23:40
반응형

스프링 개발을 하다보면 자연스레 Lombok의 @Slf4j 를 사용해 log.info() 등 로그를 남겼다. 이번 기회에 Jcl, Log4j, Slf4j, Logback용어 정리와 많이 사용되는 Logback 설정에 대해 정리해놓고자한다.


스프링 부트는 어떤 Logger를 사용하고 있나

스프링에는 크게 Jcl(Jakarta Common Logging), Slf4j 두개의 로깅 추상화 라이브러리가 있다.

두개 모두 인터페이스만 존재하기에 구현체가 필요한데 Jcl의 구현체는 Log4j, Slf4j의 구현체는 Logback이 있다.

스프링 부트는 기본적으로 Jcl에 구현체 Logback 이 사용된다.
spring-boot-starter 의존성에 spring-boot-starter-logging이 들어있고 안에는 logback 의존성이 들어있다.

Untitled

하지만 LogbackSlf4j의 구현체인데 어떻게 같이 사용되나 궁금할 수 있다.

다른 여러글을 찾아보면 모두 jcl-over-slf4j 의존성의 어댑터 패턴을 사용해 연결했다 써있지만 내가 쓰고 있는 스프링부트 버전에는 보이지 않아 직접 어댑터 부분을 찾아봤다.

먼저 Slf4j Logger의 구현체는 다음처럼 logback을 패키지를 사용한걸 볼 수 있다.

Untitled

이제 JCL을 로그를 사용해보자

Untitled

JCL 로그의 구체적 클래스는 다음과 같이 LogAdapterSlf4jLocationAwareLog 이 출력된다.

LogAdapterSlf4jLocationAwareLog 객체를 생성하는 부분이다.

Untitled

Slf4jLocationAwareLog의 생성자 인자로 LocationAwareLogger를 받는데 이는 위 Slf4j와 같은 slf4j.Logger를 상속받아 사용한다.

Untitled

결론은 JCL을 사용해도 결국 LogAdapter를 통해 Slf4jLogback을 사용하게 된다.

하지만 JCL은 명확한 단점(로그레벨 처리, 문자열 연산 등)들이 존재하기에 많은 개발자들이 Lombok @Slf4j 를 사용할 것 이다. @Slf4j를 사용하면 JCL을 사용하지 않고 바로 Slf4j 추상 라이브러리를 사용할 수 있다.


Logback 설정

Log Level

로그를 작성할 때 레벨을 설정해 원하는 로그만 출력시킬 수 있다. 로그 레벨은 총 5단계다.

TRACE < DEBUG < INFO < WARN < ERROR

위 순서대로 레벨을 가진다.

예를 들어 출력 레벨을 INFO 로 설정시 TRACE, DEBUG 레벨은 무시한다.

출력 레벨은 application.yml 을 통해 설정할 수 있다.

logging:
  level:
    root: debug

UntitledUntitled

debug로 설정했기 때문에 trace는 출력되지 않음을 확인할 수 있다.

만약 특정 패키지부분만 다르게 하고싶다면 다음과 같이 패키지 경로에 레벨을 설정한다.

logging:
  level:
    root: debug
    jpabook.jpashop.test: trace #특정 패키지 경로

logback-spring.xml

위처럼 .yml 을 통해 로그를 설정할 수 있지만 자세한 동작 변경을 원한다면 .xml파일을 이용한다.

먼저 src/main/resources 하위에 logback-spring.xml 파일을 생성하자.

사용되는 태그의 의미는 다음과 같다.

  • <appender>: 로그 형태 설정, 로그 메세지 출력 대상(콘솔, 파일) 결정
  • <encoder>: <appender> 하위에 위치해 로그메세지 변환 역할
  • <pattern>: <encoder> 하위에 위치해 로그의 출력 포맷설정
  • <root>: 로그 전역 설정 로그를 출력할 패키지, 레벨 설정
  • <logger>: 로그 지역 설정 additivity 값은 <root> 설정 상속 유무
  • <file>: 로그 기록할 파일명, 경로
  • <rollingPolicy>: 로그 파일을 교체하는 정책을 정하는 태그
  • <fileNamePattern>: rollingPolicy 하위에서 로그 파일 패턴 설정
  • <property>: 값을 정하는 프로퍼티
  • <springProperty>: application.yml 의 값을 가져올 수 있는 태그
  • <springProfile>: 스프링 프로필에 따라 값 설정하는 태그
  • <timeBasedFileNamingAndTriggeringPolicy>: 파일 교체하는 트리거 내용 설정
  • <maxFileSize>: 파일 최대 크기 설정(<timeBasedFileNamingAndTriggeringPolicy> 하위 설정)
  • <maxHistory>: 파일 최대 저장 기한 설정(<timeBasedFileNamingAndTriggeringPolicy> 하위 설정)
  • <totalSizeCap>: 파일 전체 크기를 설정해 넘을시 오래된 파일부터 삭제(<timeBasedFileNamingAndTriggeringPolicy> 하위 설정)

콘솔 로그 출력 설정

<!-- logback.spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 로그 출력 패턴 property -->
    <property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>

    <!-- application.yml의 source에 해당하는 값 가져옴 -->
    <springProperty scope="context" name="LOG_LEVEL" source="logging.level.test"/>

    <!-- ch.qos.logback.core.ConsoleAppender클래스를 지정하여 콘솔에 로그 출력 설정 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 위에 정의한 LOG_PATTERN으로 패턴 설정 -->
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- jpabook.jpashop.test 패키지 지역의 로그 설정 -->
    <logger name="jpabook.jpashop.test" level="warn" additivity="true">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <!-- 프로젝트 전역 로그 설정 -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>
#application.yml
logging:
  level:
    test: warn

jpabook.jpashop.test

jpabook.jpashop.test

결과

결과

위 결과를 보면 warn 2번, error 두번이 출력됐다.

이유는 <logger> 태그에 additivity=true가 선언됐기 때문인데 additivity는 부모 <logger>, <root>appender를 상속할지 여부이다.

additivity=true이면 <logger>의 부모인 <root>appender도 표현되기 때문에 두번씩 출력되는 결과를 볼 수 있다. 다시 말하면 additivity=false이면 <root>로 전달되지 않고 <logger>appneder만 출력된다.

파일 로그 출력 설정

<!-- logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 로그 패턴 -->
    <property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>

    <!-- application.yml의 source에 해당하는 값 가져옴 -->
    <springProperty scope="context" name="LOG_LEVEL" source="logging.level.test"/>

    <!-- profile이 local인 경우 property 설정-->
    <springProfile name="local">
        <property name="PATH" value="./logs/test-local.log"/>
    </springProfile>

    <!-- ch.qos.logback.core.rolling.RollingFileAppender클래스를 지정하여 여러 파일을 순회하며 로그 저장 설정 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 파일 경로 및 이름 -->
        <file>${PATH}</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <!-- 로그파일을 교체하는 정책 TimeBasedRollingPolicy: 시간 단위 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 파일 이름 -->
            <fileNamePattern>./logs/time.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 특정 액션에 따라 파일 교체 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 최대 사이즈 -->
                <maxFileSize>5KB</maxFileSize>
                <!--<maxHistory>10</maxHistory>--> <!-- 파일의 최대 저장 기한  -->
                <!--<totalSizeCap>3GB</totalSizeCap>--> <!-- 전체 파일 크기를 제어하며, 용량을 초과하면 가장 오래된 파일 삭제 -->
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="FILE"/>
    </root>
</configuration>
#application.yml
spring:
  profiles:
    active: local
logging:
  level:
    test: info

Untitled

위에서 보면 <springProfile>local로 설정했기에 test-local.log라는 이름으로 로그 파일 생성된 것을 볼 수 있다.


참고

https://goddaehee.tistory.com/206

https://dololak.tistory.com/635

반응형
Comments