Logback 로깅을 Json 형태로 저장하기

2019, Nov 05    

ELK, EFK든 중앙 로깅 시스템 진행시 엘라스틱 서치 데이터 포맷에 맞추기 위해서는 json 형태로
로깅이 전송 되어야 한다.

각 application server에서 filebeat등을 이용하여 json을 컨버팅 하는 방법도 있겠지만,
필자의 경우 queue서버(aws kinesis)로 각 application이 바로 전송하는 방식을 선택 하였기
때문에 로그백 자체에서 json으로 로그를 저장하는 방식을 찾아 보았다.

Logback JsonLayout을 이용 (https://www.baeldung.com/java-log-json-output)

  • pom.xml에 아래 dependency들을 추가
        <dependencies>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.1.7</version>
            </dependency>
    
            <dependency>
                <groupId>ch.qos.logback.contrib</groupId>
                <artifactId>logback-json-classic</artifactId>
                <version>0.1.5</version>
            </dependency>
    
            <dependency>
                <groupId>ch.qos.logback.contrib</groupId>
                <artifactId>logback-jackson</artifactId>
                <version>0.1.5</version>
            </dependency>
    
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.3</version>
            </dependency>
        </dependencies>
    
  • 로그백 설정 파일(보통은 logback-spring.xml)에 appneder 설정
    <appender name="json" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
            <jsonFormatter
                class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
                <prettyPrint>true</prettyPrint>
            </jsonFormatter>
            <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
        </layout>
    </appender>
    
    <logger name="jsonLogger" level="TRACE">
        <appender-ref ref="json" />
    </logger>
    
  • prettyPrint 설정을 true로 하면 콘솔에 이쁘게(줄바꿈 되어서) 나오긴 하지만 엘라스틱에
    개행문자(‘/n’) 가 포함되기 때문에 전송하기 위해서는 false로 설정 하는 편이 좋다.

  • 위와 같이 설정하면 기본적으로 아래와 같은 output이 나온다.
    {
      "timestamp" : "2017-12-14 23:36:22.305",
      "level" : "DEBUG",
      "thread" : "main",
      "logger" : "jsonLogger",
      "message" : "Debug log message",
      "context" : "default"
    }
    
  • json 객체에 필드를 커스텀 하여 추가 하고 싶다면 MDC를 이용해야 한다.
     MDC.put("customKey", "aaaa");
    
     #결과
     {
       "timestamp" : "2017-12-14 23:36:22.305",
       "level" : "DEBUG",
       "thread" : "main",
       "logger" : "jsonLogger",
       "message" : "Debug log message",
       "context" : "default",
       "mdc" : {
          "customKey" : "aaaa"
        }
     }
    

Logstash Logback Encoder 이용 (https://www.baeldung.com/java-application-logs-to-elastic-stack)

개별 로직에서(service나 controller) 에서 로깅한 내용을 검색하려면 message 필드를 정규식으로 검색
해야 하는데, 검색에 용이하고자 json 객체에 따로 필드로 추가 하고 싶은 경우가 있을 수 있다.

MDC를 이용하면 가능 하긴 하지만 2뎁스이상 되어야 하고 1depth에 필드를 추가 하고 싶다면,
Logstash Logback Encoder로 바꾸는게 답인것 같다.

공식 github 페이지를 보면 잘 설명 되어 있다.

  • pom.xml dependency 추가
  <dependency>
      <groupId>net.logstash.logback</groupId>
      <artifactId>logstash-logback-encoder</artifactId>
      <version>4.11</version>
  </dependency>
  • logback 설정 .xml 파일에서 아래와 같이 설정
      <appender name="STASH" class="ch.qos.logback.core.ConsoleAppender">
          <file>logback/redditApp.log</file>
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
              <fileNamePattern>logback/redditApp.%d{yyyy-MM-dd}.log</fileNamePattern>
              <maxHistory>7</maxHistory>
          </rollingPolicy>  
          <layout class="net.logstash.logback.layout.LogstashLayout"> <!-- json 포맷 -->
              <timestampPattern>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampPattern>
              <customFields><!-- 추가 커스텀 필드 -->
                  {
                      "appname" : "${app.name}",
                      "port":"${server.port:-8080}"
                  }
              </customFields>
          </layout>
      </appender>
      <root level="DEBUG">
          <appender-ref ref="STASH" />        
      </root>
  • 에 추가로 설정하고자 하는 필드를 설정 하면 application 전체에 적용 되고, 개별 로직에서 특별히 필드를 추가하고자 한다면 아래와 같이 하면 된다.
    //logstash markers append 임포트
    import static net.logstash.logback.marker.Markers.append;

    //로깅시 ppend 메소드 사용
    log.info(append("myCustomField", "hello"), "add key to log json");

output 결과

       {
         "@timestamp":"2019-11-04 19:13:01.018",
         "@version":"1",
         "message":"add key to log json",
         "logger_name":"com.sample.logappender.SampleController",
         "thread_name":"http-nio-8080-exec-3",
         "level":"INFO",
         "level_value":20000,
         "HOSTNAME":"AD01277912",
         "myCustomField":"hello",
         "appname":"appender-sample",
         "port":"8080"
       }

결론

json 필드를 커스텀 하기에는 logstash-encoder를 활용하는 것이 편리한 듯 하다.
테스트로 logstash-encoder 에서 MDC.put을 이용해 보았는데 mdc 객체 필드가 생기지
않고 바로 json 객체에 해당 key가 생성 되었다.