LogBack


로그 남기기

오늘은 스프링 부트 서버를 돌릴때 로그를 남기는 방법에 대해서 공부한 뒤 프로젝트에 적용해 봤다.

사실 왜 로그를 쌓아야 하고, 쌓은 로그를 통해 어떤 아웃풋을 얻을 수 있는지 이해가 가지 않았다.

그래서 우선 로그를 기록해야 하는 이유에 대해서 알아봤다.


왜 로그를 기록하고 분석해야 할까?

1) 외부로부터의 침입 감지와 추적

2) 시스템 성능 관리와 시스템의 장애 원인 분석

3) 시스템 취약점 분석

4) 침해 사고 시 ‘근거 자료’로 활용

5) 각종 법규나 지침에서 관리 의무화(개인정보보호법의 안전성 확보 조치 기준)

6) 기타 등등…


아마 지금하는 프로젝트에서는 2번이 가장 큰 이유가 아닐까 싶다.



Spring Boot 로그 남기기

우선 logback-spring.xml 이라는 파일을 하나 생성해준다.

에러 발생

처음에는 logback.xml이라는 명으로 파일을 생성하였는데, LOG_PATH_IS_UNDEFINED 라는

폴더가 생성되었다. stackoverflow에서 검색해 본 결과, springboot를 실행할 때 파일을

읽어오는 순서가 있는데 application.properties를 먼저 읽고 xml 파일을 읽어야 하는데,

그 순서가 반대로 되어서 LOG_PATH_IS_UNDEFINED 폴더가 생성되는 이슈가 있었다.

해당 문제에 대한 공식문서 팁



로그 파일 작성

코드가 너무 길어서 태그 별로 뜯어서 정리…


[ configuration 설정 ]

우선 제일 상단에 있는 <configuration> 태그 안에 모든 태그가 들어간다.

→ 10초마다 설정 파일의 변경을 확인하여 변경시 갱신

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
    <!-- 모든 설정들이 이 자리에 들어간다. -->
</configuration>


[ springProfile 설정 ]

→ spring.profiles에 따른 설정파일 분기

<springProfile name = "dev">
    <property resource = "application-dev.properties"/>
</springProfile>

<springProfile name = "prod">
    <property resource = "application-prod.properties"/>
</springProfile>

참고로 application-dev.properties와 application-prod.properties 파일에 각각

아래와 같이 설정을 추개해줘야 한다.

// application-dev.properties
log.config.level=debug
log.config.path=./application_log/dev
log.config.filename=dev_log
log.config.filename.error=dev_error

// application-prod.properties
log.config.level=info
log.config.path=./application_log/prod
log.config.filename=prod_log
log.config.filename.error=prod_error


[ property 설정 ]

→ “logback-spring.xml”에서 사용할 상수들을 설정한다.

${} 안에 있는 변수들은 application.properties에서 설정해준 값들이다.

<!-- 루트 로그 레벨 -->
<property name ="LOG_LEVEL" value = "${log.config.level}"/>

<!-- 로그 파일 경로 -->
<property name ="LOG_PATH" value = "${log.config.path}"/>

<!-- 로그 파일 명 -->
<property name ="LOG_FILE_NAME" value = "${log.config.filename}"/>
<property name ="ERR_LOG_FILE_NAME" value = "${log.config.filename.error}"/>

<!-- 로그 파일 패턴 -->
<property name ="LOG_PATTERN" value ="%-5level %d{yyyy-MM-dd HH:mm:ss}[%thread] [%C] [%logger{0}:%line] - %msg%n"/>

추가적으로 마지막에 있는 로그 파일 패턴 을 정리해둔 블로그를 참고했다.

로그 패턴설명
%-5level로그 레벨
%d{yyyy-mm-dd HH:mm:ss}%d는 date를 의미하며, 중괄호에 들어간 문자열은 dataformat을 의미.
%c로깅이 발생한 카테고리
%logger{0}패키지를 제외한 클래스 이름만 출력
%line로깅이 발생한 호출지의 라인


[ appender 설정 ]

→ log의 형태를 결정, 로그 메세지가 출력될 대상을 결정하는 요소

appender의 class의 종류설명
ch.qos.logback.core.ConsoleAppender콘솔에 로그를 찍음, 로그를 OutputStream에 작성하여 콘솔에 출력되도록 한다.
ch.qos.logback.core.FileAppender파일에 로그를 찍음, 최대 보관 일 수 등를 지정할 수 있다.
ch.qos.logback.core.rolling.RollingFileAppender여러개의 파일을 롤링, 순회하면서 로그를 찍는다.(FileAppender를 상속 받는다.지정 용량이 넘어간 Log File을 넘버링 하여 나누어 저장할 수 있다.)
ch.qos.logback.classic.net.SMTPAppender로그를 메일에 찍어 보낸다.
ch.qos.logback.classic.db.DBAppenderDB(데이터베이스)에 로그를 찍는다.


콘솔 Appender 설정

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


파일 Appender 설정

<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>

    <!-- 롤링 정책 -->
    <rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- gz, zip 등을 넣을 경우 자동 로그파일 압축 -->
        <fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${LOG_FILE_NAME}_%i.log</fileNamePattern>

        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- 파일당 최고 용량 -->
            <maxFileSize>10MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>

        <!-- 로그파일 최대 보관주기 -->
        <maxHistory>30</maxHistory>
    </rollingPolicy>
</appender>


파일 Appender 설정(Only Error)

<appender name = "ERROR" class ="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 로그 레벨 : ERROR만 설정) -->
    <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>

    <!-- 롤링 정책 -->
    <rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- gz, zip 등을 넣을 경우 자동 로그파일 압축 -->
        <fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${ERR_LOG_FILE_NAME}_%i.log</fileNamePattern>

        <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- 파일당 최고 용량 -->
            <maxFileSize>10MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>

        <!-- 로그파일 최대 보관주기 -->
        <!-- totalSizeCap을 사용하려면 maxHistory를 필수로 정의해줘야 한다. -->
        <maxHistory>30</maxHistory>
        <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
</appender>
rollingPolicy Class설명
ch.qos.logback.core.rolling.TimeBasedRollingPolicy일자별 적용
ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP일자별 + 크기별 적용


[ root, logger 설정 ]

→ 설정한 appender를 참조하여 package와 level을 설정한다.


root(전역 설정)

<root level = "${LOG_LEVEL}">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="ERROR"/>
</root>


logger(지역 설정)

🔎 설명 추가 예정

<logger name="org.springframework" level = "INFO" additivity = "false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="ERROR"/>
</logger>

<logger name="org.hibernate.validator" level = "INFO" additivity = "false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="ERROR"/>
</logger>

<logger name="com.zaxxer.hikari" level = "INFO" additivity = "false">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
    <appender-ref ref="ERROR"/>
</logger>


[ logback-spring.xml 완성본]

<configuration scan="true" scanPeriod="10 seconds">

    <springProfile name = "dev">
        <property resource = "application-dev.properties"/>
    </springProfile>

    <springProfile name = "prod">
        <property resource = "application-prod.properties"/>
    </springProfile>


    <property name ="LOG_LEVEL" value = "${log.config.level}"/>
    <property name ="LOG_PATH" value = "${log.config.path}"/>
    <property name ="LOG_FILE_NAME" value = "${log.config.filename}"/>
    <property name ="ERR_LOG_FILE_NAME" value = "${log.config.filename.error}"/>
    <property name ="LOG_PATTERN" value ="%-5level %d{yyyy-MM-dd HH:mm:ss}[%thread] [%C] [%logger{0}:%line] - %msg%n"/>

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

    <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>

        <rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${LOG_FILE_NAME}_%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </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_LOG_FILE_NAME}.log</file>

        <encoder class = "ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>

        <rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${ERR_LOG_FILE_NAME}_%i.log</fileNamePattern>

            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <root level = "${LOG_LEVEL}">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR"/>
    </root>

    <logger name="org.springframework" level = "INFO" additivity = "false">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR"/>
    </logger>

    <logger name="org.hibernate.validator" level = "INFO" additivity = "false">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR"/>
    </logger>

    <logger name="com.zaxxer.hikari" level = "INFO" additivity = "false">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR"/>
    </logger>
</configuration>


참고 블로그

📚 관련 블로그 1

📚 관련 블로그 2

📚 관련 블로그 3

📚 관련 블로그 4