티스토리 뷰

공부흔적/자바

Logback 설정에 관하여

주디 𝙹𝚞𝚍𝚢 2022. 2. 23. 23:55

log 설정도 한 번 봐야지, 봐야지 하다가 오늘 마침 설정 건드린 김에 정리해본다. 오늘 내가 설정 수정한 내용은 시스템 분리였다. 기존 A시스템이 있는 상태였고, 여기에 B시스템을 새로 추가하면서 로그를 따로 관리하게 되어 분리해야했다.(기존 시스템에 새로운 시스템을 추가하는 것에 대한 논의는 차치하고.)


logback 설정파일

 일반적으로 logback 설정파일은 src/main/resources 아래에 logback.xml로 존재한다. 구조는 <configuration>, <configuration> 안에 <appender>(선택), <logger>(선택), 최대 1개의 <root>로 구성되어 있다.

<configuration>
    <appender></appender> // 선택
    <logger></logger> // 선택
    <root> // 최대 1개
</configuration>

 참고로 logback 0.9.17버전부터는 태그 이름의 경우, 대소문자를 구분하지 않는다고 한다. 예를 들어, <logger>, <Logger>, <LOGGER>는 모두 <logger>이다. 다만, <logger></Logger> 이런 식으로 구성할 수는 없다. <logger>로 시작했다면 닫을 때도 </logger>여야 한다.

* <appender>

 로그가 출력될 위치를 나타낸다. 기본적인 속성으로 name과 class를 갖는다. name은 이름을 지정, class는 인스턴스화할 Appender 인터페이스를 구현한 클래스 이름을 지정한다. 내부 구조를 뜯어보면 아래와 같다. (Intellij에서 Appender 인터페이스로 이동한 뒤 ctrl + H(맥기준)를 누르면 계층 구조를 확인할 수 있다. 계층 구조 조회는 Intellij Ultimate만 지원되는 기능이다.) 이름만 보고도 알텐데 ConsoleAppender는 콘솔 출력, FileAppender는 파일 출력이다. 아래에서보다시피 콘솔, 파일, 소켓, 메일, DB 등 여러 방법으로 로그를 출력할 수 있다는 것을 알 수 있다.

 - ConsoleAppender의 경우, 아래와 같은 세 가지 속성을 갖는다.

 1. encoder : 어떤 식으로 작성할지 내부에 있을 <pattern>으로 지정 가능하다. 이때 어떻게 pattern을 지정하는지에 대해서는 🔗 여기를 참고하면 된다.

 2. target : "System.out"과 "System.err"가 있다. 어떻게 알 수 있는지 확인하고 싶다면 직접 ConsoleAppender 클래스를 까보면 된다.

더보기

아래를 보면 ConsoleTarget가 ConsoleAppender의 target 필드라는 것을 알 수 있다.

public class ConsoleAppender<E> extends OutputStreamAppender<E> {
    protected ConsoleTarget target;
    protected boolean withJansi;
    private static final String WindowsAnsiOutputStream_CLASS_NAME = "org.fusesource.jansi.WindowsAnsiOutputStream";

	...

 그래서 ConsoleTarget 내부를 까보면 아래와 같다. enum ConsoleTarget은 SystemOut과 SystemErr가 있고, name으로 각각 "System.out"과 "System.err"를 갖는다.

package ch.qos.logback.core.joran.spi;

import java.io.IOException;
import java.io.OutputStream;

public enum ConsoleTarget {
    SystemOut("System.out", new OutputStream() {
        public void write(int b) throws IOException {
            System.out.write(b);
        }

        public void write(byte[] b) throws IOException {
            System.out.write(b);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            System.out.write(b, off, len);
        }

        public void flush() throws IOException {
            System.out.flush();
        }
    }),
    SystemErr("System.err", new OutputStream() {
        public void write(int b) throws IOException {
            System.err.write(b);
        }

        public void write(byte[] b) throws IOException {
            System.err.write(b);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            System.err.write(b, off, len);
        }

        public void flush() throws IOException {
            System.err.flush();
        }
    });
    
    private final String name;
    private final OutputStream stream;

 ConsoleAppender말고 RollingFileAppender도 있는데, 한 파일에 로그를 기록하다가 어느 조건을 만족시키면 다른 파일에 로그를 기록한다.

 3. withJansi : 기본값은 false인데 true로 지정하면 Jansi 라이브러리를 사용해서 색이 입혀진다.

 * <logger>

 0개 이상의 <appender-ref>를 포함할 수 있고, 참조되는 appender는 해당 logger에 추가된다.

* <root>

root 로거를 나타낸다. <appender-ref>를 선택적으로 포함한다.


예시

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration debug="true">

    <property name="BASE_DIR" value="/Users/ara/logging" />

    // 콘솔로 출력
    <appender name="CONSOLE_LOG" class="ch.qos.logback.core.ConsoleAppender"> // 이벤트가 발생하면 특정 레이아웃을 사용하여 System.out or System.err를 수행한다.
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss}\t[%-5level] : %msg%n</pattern>
        </encoder>
    </appender>

    // 로그 레벨로 분리해서 출력
    // 아래는 에러로그를 파일로 출력하는 코드
    <appender name="ERROR_LOG" 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>${BASE_DIR}/error_log.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${BASE_DIR}/backup/error_log.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss}\t[%-5level] : %msg%n</pattern>
        </encoder>
    </appender>

    // 파일로 출력
    <appender name="FILE_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${BASE_DIR}/base_log.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${BASE_DIR}/backup/base_log.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss}\t[%-5level] : %msg%n</pattern>
        </encoder>
    </appender>

    <root>
        <appender-ref ref="CONSOLE_LOG" />
    </root>

    // consoleLog라는 이름으로 호출하면 이 로거를 사용
    <logger name="consoleLog" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE_LOG" />
    </logger>

    // fileLog라는 이름으로 호출하면 이 로거를 사용
    <logger name="fileLog" level="DEBUG" additivity="false">
        <appender-ref ref="FILE_LOG" />
    </logger>

    // com.test.logging2 패키지의 로거는 이 로거를 사용
    <logger name="com.test.logging.v2" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE_LOG" />
        <appender-ref ref="ERROR_LOG" />
    </logger>

</configuration>

만든 로거를 어떻게 호출할까?

위의 예시파일을 작성하면 원하는 로거의 구성이 끝났다. 그렇다면 이 로거를 어떻게 호출할까? 바로 org.slf4j의 LoggerFactory를 사용한다.

@Controller
public class LogController {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogController.class);

}

 getLogger의 파라미터로 <logger>의 이름을 사용하거나,  클래스명.class을 사용할 수 있다. <logger>의 이름을 사용하게 되면 그 logger를 가져다쓰게 되고, 클래스명.class를 사용하게 되면 해당 클래스가 만약 com.test.logging.v2 패키지에 속해있다면 이 패키지명이 이름인 logger를 사용하게 된다. 따로 logger를 지정해주지 않았다면 <root>를 따른다.

 그래서 만약 LogController 코드가 아래와 같다면,

package com.test.logging.v1;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
public class LogControllerV1 {

    private static final Logger LOGGER = LoggerFactory.getLogger(LogControllerV1.class);

    private static final Logger F_LOGGER = LoggerFactory.getLogger("fileLog");

    @ResponseBody
    @GetMapping(value="/v1")
    public String hello(HttpServletRequest req) {
        LOGGER.info("[{}] {}", req.getRemoteAddr(), "Hello!");
        F_LOGGER.info("[file {}] {}", req.getRemoteAddr(), "Hello!");
        return "Hello";
    }

}

 실행하게 되면 첫번째 로그는 LogControllerV1.class로 logger를 찾는데, 이 클래스가 속한 패키지인 com.test.logging.v1을 이름으로 하는 로거가 따로 설정되어 있지 않기 때문에 <root>를 이용해서 CONSOLE_LOG로 출력된다.

 하지만 두번째 로그는 fileLog라는 이름을 가진 Logger를 가져다 사용하므로 BASE_DIR/base_log.log에 그 내용이 출력된다.

300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함