Joswlv

LogBack in SpringBoot

2016-10-13

LogBack 이란

logback은 SLF4J의 native 구현체이다. slf4j로 어플리케이션 로그를 남긴다면 logback을 선택하는 것이 좋다고 한다. slf4j의 도움으로 연관 라이브어리들이 다른 loggin framework를 쓰더라도 logback으로 통합할 수 있다.

logbac은 logback-core, logback-classic, logback-access의 3개의 모듈이 있다.

logback.xml설정

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
        </layout>
    </appender>
 
    <logger name="example.logback.level.grandparents" level="TRACE"/>
    <logger name="example.logback.level.grandparents.parents.children" level="INFO"/>
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

여기서 root는 root logger를 말한다. logger name=”“과 같은 뜻이라고 생각하면 된다.

<logger name="example.logback.level.grandparents" level="TRACE"/>

example.logback.level.grandparents 이하 모든 Logger들의 level은 TRACE라는 설정이다.

log level은 다음과 같다.

  • trace
  • debug
  • info
  • warn
  • error

Appender

Event마다 Log를 기록하는 기능은 Appender가 처리한다. 그래서 Logger는 어떤 Appender에 해당이 되어 처리 되는지가 중요하다. Appender를 설정해도 log출력에 해당되지 않으면 작동하지 않는다.

Appender는 출력될 형식을 직접 가지고 있이 않고, 해당 기능은 Layout과 Encoder에 위임한다.

ConsoleAppender

ConsoleAppender는 OutputStreamAppender를 상속한다. encoder, pattern으로 PatternLayoutEncoder가 생성해서 Appender에 주입된다.

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  <!-- encoders are assigned the type
       ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  <encoder>
    <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
  </encoder>
</appender>

PatternLayoutEncoder는 pattern으로 받은 값을 이용해서 PatternLayout을 생성하고 PatternLayoutEncoder는 log messag를 byte[]로 변환하는 기능도 포함한다. 이렇게 되면 Appender는 Layout기능과 Encoder기능을 모두 가지게 된다. 이것을 이용해 OutputStreamAppender는 byte[]를 OutputStream에 write하게 된다.

참고 - http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout

FileAppender

<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
     the key "bySecond" into the logger context. This value will be
     available to all subsequent configuration elements. -->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
  <file>log-${bySecond}.txt</file>
  <append>true</append>
  <!-- encoders are assigned the type
       ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
  <encoder>
    <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
  </encoder>
</appender>

로그가 저장될 file을 선언하고, ConsoleAppender처럼 encoder, pattern을 선언하게 되면, log event를 지정된 file에 저장할 수 있다. 이때 파일 포맷중 날짜 형식은 java.text.SimpleDateFormat을 따른다.

<file>태그에 파일명만 작성하면 톰켓으로 구동할 경우 톰켓 bin폴더에 로그파일이 생성된다.

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
...
  <prudent>true</prudent>
...
</appender>

<prudent>태그는 file에 저장될때 lock을 생성해서 처리한다. 서로 다른 java vm이 같은 파일을 가릴킬때 사용한다. 성능이 저하되는 단점이 있다.

RollingFileAppender

log가 많아지면 file하나당 최대 용량 제한도 있고, 로그를 파악하기도 어렵다. 이때 대부분 날짜를 기준으로 file을 남긴다. 따로 crontab으로 매일 file을 rename해서 처리할 수도 있지만, logback은 RollingFileAppender로 처리할 수 있다.

<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>mylog.txt</file>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- rollover daily -->
    <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy
          class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
      <!-- or whenever the file size reaches 100MB -->
      <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
  </rollingPolicy>
  <encoder>
    <pattern>%msg%n</pattern>
  </encoder>
</appender>

rollingPolicy로 rolling정책을 등록할 수 있다. fileNamePattern으로 file pattern을 선언하고, <timeBasedFileNameingAndTriggeringPolicy>로 파일마다 트리거를 걸어 파일 최대 용량을 설정하면 새로운 index이름으로 파일을 생성한다.

filesize는 끝문자열에 kb,mb,gb를 인식하고 대소문자 구분은 없다. whitespace는 모두 무시한다.

<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt.zip</fileNamePattern>

확장자에 .zip을 선언하면 새로운 file이 생성될때 이전 파일은 .zip으로 압축을 할 수 있다.

Configuration

logback debug모드

아래 설정으로 logback이 구동될때 logback상태를 확인 할 수 있다.

<configuration debug="true">
...
</configuration>

additivity

additivity의 default값은 true이다. logger name이하 모두 적용이 되는데 additivity를 false로 설정하면 해당 name에만 looger가 적용된다.

<logger name="XXX" level="DEBUG" additivity="false"/>

root

loggr들은 name으로 등록이 된다. 기본적으로 java package구조와 동일하게 적용한다. tree구조이기때문에 최상단 root을 적용하면 모든 tree이하에 적용 할 수 있다. 이 설정으로 logger를 대신한다.

<root level="DEBUG">
    <appender-ref ref="STDOUT" />
</root>

모든 대상에 STDOUT Appender를 적용하고 level이 DEBUG이하인 것만 처리로 설정된다.

Json으로 log남겨 보기!

간단한 쇼핑몰 log를 json으로 남겨본다.

우선 logback.xml대신에 logback-sping.xml파일을 만든다.

내용은 아래와 같다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <appender name="FILE-AUDIT"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/tmp/test.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>
                %msg%n
            </Pattern>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>${DEV_HOME}/archived/debug.%d{yyyy-MM-dd}.%i.log
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>
    <logger name="com.newace.model" level="TRACE" additivity="false">
        <appender-ref ref="FILE-AUDIT"/>
    </logger>

</configuration>

logger를 com.newace.model아래에 TRACE level아래의 모든 로그로 설정하였다.

log모델은 다음과 같다.

public class LogSumModel extends BaseModel {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogSumModel.class);

    private static final LogSumModel LOG_LOGIN = new LogSumModel("login");
    private static final LogSumModel LOG_ADD_CART = new LogSumModel("cart");
    private static final LogSumModel LOG_CATEGORY = new LogSumModel("category");
    private static final LogSumModel LOG_PRODUCT_BUY = new LogSumModel("buy");
    private static final LogSumModel LOG_PRODUCT_DETAIL = new LogSumModel("detail");


    private String action;
    private long regDate;
    private int userId;
    private int categoryNum;
    private int productNum;
    private boolean isCart;

    private LogSumModel() {
    }

    private LogSumModel(String action) {
        this();
        this.action = action;
    }

    public static void writeLoginLog(@NotNull UserModel userModel) {
        LOGGER.trace(LOG_LOGIN.setRegDate(System.currentTimeMillis())
                .setUserId(userModel.getUserId())
                .toString());
    }

    public static void writeAddCartLog(@NotNull UserModel userModel, @NotNull ProductModel productModel) {
        LOGGER.trace(LOG_ADD_CART.setRegDate(System.currentTimeMillis())
                .setProductNum(productModel.getProductId())
                .setUserId(userModel.getUserId())
                .toString());
    }

    public static void writeProductBuyLog(@NotNull UserModel userModel, @NotNull ProductModel productModel, boolean isCart) {
        LOGGER.trace(LOG_PRODUCT_BUY.setRegDate(System.currentTimeMillis())
                .setProductNum(productModel.getProductId())
                .setUserId(userModel.getUserId())
                .setCart(isCart)
                .toString());
    }

    public static void writeProductDetailLog(@NotNull UserModel userModel, @NotNull ProductModel productModel) {
        LOGGER.trace(LOG_PRODUCT_DETAIL.setRegDate(System.currentTimeMillis())
                .setProductNum(productModel.getProductId())
                .setUserId(userModel.getUserId())
                .toString());
    }

    public static void writeCategoryLog(@NotNull UserModel userModel, @NotNull CategoryModel productModel) {
        LOGGER.trace(LOG_CATEGORY.setRegDate(System.currentTimeMillis())
                .setUserId(userModel.getUserId())
                .setCategoryNum(productModel.getCategoryId())
                .toString());
    }

.... SeterGeter메소드

}

BaseModel에서는 object를 json으로 변환하는 함수를 상속해준다.

public class BaseModel {
    private static ObjectMapper mapper = new ObjectMapper();

    @Override
    public String toString() {
        try {
            return mapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

로그 모델객체를 생성해 로그를 찍고 싶은 곳에서 사용하면 된다. 결과는 아래그림과 같다.


Reference


next Sqoop

Comments