Spring Boot, Graylog로 GELF UDP 로그 전송하기

개요

  • Graylog는 분산 애플리케이션에 대한 중앙 로그 수집 및 조회 기능을 제공하는 통합 로깅 플랫폼이다. Key-Value 기반의 독자적인 GELF 로그 포맷이 지원하며 최근 릴리즈된 v2.3.0에서는 최신 버전의 ElasticSearch 5.x를 지원한다. [관련 링크] 이번 글에서는 Spring Boot 기반의 애플리케이션에서 GELF UDP 로그를 전송하는 방법을 설명하고자 한다.

앞서 읽어볼만한 글

목표

  • Spring Boot 기반으로 구조화된 GELF UDP 로그를 전송할 수 있다.

구조화된 로그가 필요한 이유

  • AI의 등장과 함께 이제 애플리케이션 로그는 사람이 보는 정보가 아니라 기계가 분석하는 정보가 되었다. 기계가 로그를 분석하려면 기존의 전통적인 라인 단위의 텍스트 로그로는 부족하다. 잘 설계된 구조화된 로그를 전송하면 기계에 의해 분석되어 유의미한 데이터로 가공될 수 있다. 구조화된 GELF 로그 포맷은 그러한 목적으로 탄생하였다. [관련 링크]

라이브러리 종속성 추가

  • 프로젝트 루트의 build.gradle에 아래 내용을 추가한다.
dependencies {
    compile group: 'biz.paluch.logging', name: 'logstash-gelf', version: '1.11.1'
}
  • logstash-gelf 아티팩트는 다양한 로깅 라이브러리에서 GELF(TCP/UDP) 로깅을 가능하게 해준다. 본 예제는 Spring Boot 기반이므로 기본 로깅 라이브러리인 Logback을 기준으로 설명한다.

로깅 작성 예

@Slf4j(topic = "GELF")
public class SomeClass {

    public someMethod() {

        log.info("SOME_METHOD_FINISHED");
    }
}
  • GELF 로깅을 작성할 클래스에서는 @Slf4j(topic = {LoggerName})과 같이 클래스 레벨에 로거의 이름을 명시해주어야 한다. 위 예제에서는 일반 로거와의 쉬운 구분을 위해 로거의 이름으로 GELF를 명시해주었는데 명명은 자유롭게 해도 무방하다. (일반적으로 로거의 이름은 클래스를 포함한 패키지명으로 명명하는 것이 관행이다.)

  • log.info({message})가 실행되는 즉시 GELF 형식의 로그가 Graylog 서버로 전송된다. 아규먼트로 전달된 message 값은 GELF 포맷의 short_message, full_message 필드에 담겨 전송된다. 나머지 필수 필드는 logstash-gelf 라이브러리에 의해 자동 생성되어 전송된다. 실제 전송되는 필드 정보는 아래와 같다.

{
    "LoggerName": "GELF",
    "Severity": "INFO",
    "SourceClassName": "com.jsonobject.example.SomeClass",
    "SourceLineNumber": 6,
    "SourceMethodName": "someMethod",
    "SourceSimpleClassName": "SomeClass",
    "Thread": "main",
    "Time": "2017-09-06 17:44:33,0919"
    "facility": "logstash-gelf",
    "message": "SOME_METHOD_FINISHED",
    "full_message": "SOME_METHOD_FINISHED",
    "level": 6,
    "source": "jsonobejct",
    "timestamp": "2017-09-06T08:44:33.919Z"
}
  • GELF 메시지의 원본은 위와 같은 JSON 문자열이다. 이 문자열은 어펜더에 의해 전송 전 GZIP으로 압축되는데 UDP의 경우 압축된 총 크기가 8,192바이트를 초과할 경우 분할 전송된다. 이 경우 문제는 메시지 인풋이 실행 중인 Graylog 서버가 로드 밸런싱되고 있을 경우 분할 전송된 패킷 조차 로드 밸런싱되어 메시지가 유실된다. 따라서 압축된 로그 메시지의 크기가 8,192바이트를 초과하지 않도록 설계하는 것이 바람직하다. 애플리케이션에서 발생하는 대부분의 로그는 이 크기 안에서 소화가 가능하다.

커스텀 필드 전송

  • GELF는 필드명의 앞에 _가 붙는 커스텀 필드(additional field)를 지원한다. 아래와 같이 커스텀 필드를 추가할 수 있다.
MDC.put("some_field", "some_value");
MDC.put("another_field", "another_value");
log.info("SOME_METHOD_FINISHED");
MDC.clear();
  • 위 방법은 Slf4j가 제공하는 MDC를 이용한 것이다. MDC는 쓰레드 단위의 생명주기를 가지며 원하는 추가 필드를 담을 수 있다. 번거롭지만 쓰레드 풀 환경에서 엉뚱한 커스텀 필드가 남겨지는 것을 예방하기 위해 반드시 MDC.clear()로 로깅을 마무리해야 한다.
  • 이 부분을 조금이라도 효율적으로 보완하자면 아래와 같은 유틸리티 클래스를 만드는 방법도 있다.
package com.jsonobject.example.util;

import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor
public class GelfCustomFields {

    private List<String> fieldNames;

    public GelfCustomFields add(String fieldName, String fieldValue) {

        if (this.fieldNames == null) {
            this.fieldNames = new ArrayList<>();
        }
        if (!StringUtils.isEmpty(MDC.get(fieldName))) {
            return this;
        }
        if (StringUtils.isEmpty(fieldValue)) {
            return this;
        }
        this.fieldNames.add(fieldName);
        MDC.put(fieldName, fieldValue);

        return this;
    }

    public void clear() {

        if (this.fieldNames != null) {
            for (String fieldName : this.fieldNames) {
                MDC.remove(fieldName);
            }
        }
    }
}
  • 사용 예는 아래와 같다.
{
    GelfCustomFields fields = new GelfCustomFields()
             .add("some_filed", "some_value")
             .add("another_field", "another_value");

    log.info("SOME_METHOD_FINISHED");
    fields.clear();
}

Logback 환경설정

  • 프로젝트 루트의 /src/main/resources/logback-spring.xml 파일을 아래와 같이 작성한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>

    <appender name="GELF_UDP" class="biz.paluch.logging.gelf.logback.GelfLogbackAppender">
        <host>udp:{hostname}</host>
        <port>{port}</port>
        <version>1.1</version>
        <extractStackTrace>true</extractStackTrace>
        <filterStackTrace>true</filterStackTrace>
        <mdcProfiling>true</mdcProfiling>
        <timestampPattern>yyyy-MM-dd HH:mm:ss,SSSS</timestampPattern>
        <includeFullMdc>true</includeFullMdc>
        <maximumMessageSize>8192</maximumMessageSize>
    </appender>

    <logger name="GELF" level="INFO" additivity="false">
        <appender-ref ref="GELF_UDP"/>
    </logger>

</configuration>
  • host에는 Graylog 서버의 호스트명(도메인 또는 IP 주소)을 입력한다.
  • port에는 Graylog 서버에서 실행 중인 GELF UDP Input의 포트명을 입력한다.
  • 로거 이름이 GELF인 로그들은 모두 GELF_UDP 어펜더를 통해 전송되도록 작성했다.

참고 글


저작자 표시 비영리 동일 조건 변경 허락
신고

'SW 개발 > Spring > Logback' 카테고리의 다른 글

Spring Boot, Graylog로 GELF UDP 로그 전송하기  (0) 2017.09.06