코딩은 내일부터

분산환경에서 로깅(Logging)하는 방법(MDC, ELK, Kafka) 본문

카테고리 없음

분산환경에서 로깅(Logging)하는 방법(MDC, ELK, Kafka)

zl존 비버 2024. 2. 19. 00:57
728x90

분산 서비스 환경에서 로깅을 하는 방법과 중앙 집중형 로그 관리 하는 법까지 포스팅할 예정입니다.

 

로깅(Logging)란?

우리의 시스템이 어떻게 동작했는지에 대한 기록하는 것이다.

개발환경에서는 System.out.print을 사용해서 시스템의 동작 또는 값을 확인할 수 있지만, 배포환경에서 동작 상태를 확인하려면

System.out.print과 같은 방법으로는 한계가 있다는 것을 알아차릴 거다.

이러한 한계를 극복하고 시스템의 동작상태, 장애등을 기록할 수 있는 방법이 로깅프레임워크를 활용하는 것이다.

 

 

어떤 프레임워크가 있나요?

Spring Boot에서는 Slf4j를 기본적으로 로깅 추상화 라이브러리로 사용하기를 채택했다.

Slf4j는 추상화 라이브러리이기 때문에 개발자가 구현체를 설정해 줘야 사용하능하다.

 

대표적으로 Logback과 Log4j2를 많이 사용한다.

(이 부분은 개발자가 원하는 구현체를 사용하면 될 거 같다.)

 

 

그럼 로깅을 어떻게 하나요?

로깅을 할 때 보통 yml 또는 xml형식의 파일을 설정해 지정된 경로에 log파일을 생성한다.

        pattern: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} - %msg%n"

위와 같은 형식으로 로그를 남긴다.

하지만 멀티 스레드 환경 그리고 분산 환경에서 서비스가 동작하고 있을 때는 동시에 요청이 처리되고

server1에서 요청을 처리하고 service2로 요청이 넘어갔을 때 추적을 하기 어렵다.

 

정리해 보면 

  1. 멀티 스레드 환경에서 로그를 보기 어렵다.
  2. 분산 환경에서 로그를 추적하기 어렵다.

이렇게 2가지의 문제점이 있는데 하나하나 해결해 보겠다.

 

 

문제 1) 멀티 스레드 환경에서 로그를 보기 어렵다.

멀티스레드 환경에서는 여러 요청이 동시에 처리되기 때문에 동일한 요청에 대한 로그가 연속적으로 쌓이는 게 아니라

순서 없이 쌓인다.

이러한 로그에 요청 별로 고유 값을 부여하면 문제를 해결할 수 있다.

 

먼저 학습해야 할 것은 MDC라는 키워드이다.

MDC란?

MDC는 Mapped Diagnostic Context의 약자로 현재 실행 중인 스레드에 메타 정보를 넣고 관리하는 공간을 의미한다.

내부적으로 Map을 관리하고 있어 key, value형태로 값을 저장할 수 있다.

다시 말하면 쓰레드 단위로 관리되는 Map이라고 할 수 있다.

 

MDC필터 구현 및 로그 남기기

스프링에서 MDC에 uuid를 만들어 넣을 만하곳은 필터, 인터셉터 등이 있지만 앞단에 있는 필터에 적용하면 좋다. 왜냐하면 맨 앞단에 필터를 위치시키면 다른 필터나 인터셉터 등이 실행되기 전에 해당 정보를 설정할 수 있기 때문에, 전체 요청 처리에 대한 일관된 콘텍스트를 제공할 수 있다.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
internal class MDCLoggingFilter : Filter {
    @Throws(IOException::class, ServletException::class)
    override fun doFilter(servletRequest: ServletRequest?, servletResponse: ServletResponse?, filterChain: FilterChain) {
        val uuid: UUID = UUID.randomUUID()
        MDC.put("requestId", uuid.toString())
        filterChain.doFilter(servletRequest, servletResponse)
        MDC.clear()
    }
}

코드를 보면 필터에서 MDC를 사용하고 Order를 HIGHEST_PRECEDENCE를 사용한 이유도 맨앞단 필터로 위치시켜서 요청을 오면 제일 먼저 정보를 설정할 수 있도록 할 수 있다.

여기서 주의할 점은 doFilter가 끝나서 나오면 clear를 해준다는 것이다.
Spring MVC는 스레드 풀에 쓰레드들을 만들어 두고, 요청이 오면 스레드를 사용해 요청을 처리하고 반납한다. 그런데 MDC는 쓰레드 별로 저장되는 쓰레드 로컬을 사용하므로, 요청이 완료될 때 Clear를 해주지 않으면 다른 요청이 이 쓰레드를 재사용할 때 이전 데이터가 남아있을 수 있다.

 

마지막으로 Log의 설정파일에 다음과 같은 패턴을 추가시켜 주면 

%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] [%thread] %-5level %logger{36} - %msg%n

위와 같이 requestID로 요청을 묶어서 로그를 볼 수 있다.

 

 

문제 2) 분산 환경에서 로그를 추적하기 어렵다.

문제 1을 처리할 때처럼 분산환경에서 로그를 처리하면 위 그림에 나타난 거와 같이 유저정보 조회요청은 1,2를 거치는

즉, 하나의 동작이지만 로그에서는 다른 RequestId로 남게 된다.

 

이러한 문제는 어떻게 처리할까?

정답은 간단하다 헤더에 Trace Id를 적제 해서 요청을 보내 각각의 마이크로서비스에서 요청헤더에 Trace Id를 꺼내오면 된다.

즉, 요청을 보낼 때 Trace Id를 같이 보내주면 된다. 그렇게 하면 밑에 그림과 같이 하나의 작업(인증인가, 유저정보조회)을 Trace Id와 묶어서 볼 수 있게 된다.

이렇게 분산 환경에서 logging 하는 법을 알아봤다.

하지만 여러 서버에서 로그를 관리하면 MSA환경에서는 로그를 관리하기 어려울 것이다.

중앙 집중형으로 로그를 관리하는 방법은 없을까?

이 방법으로는 ELK를 사용하면 된다.

 

그러면 진짜 마지막으로 ELK를 설명하고 마치겠다.

 

 

ELK 그리고 Kafa를 곁들여서 로그를 한 번에 관리해 보자

ELK란?

ELK는 위 그림과 같이, 분석 및 저장 기능을 담당하는 ElasticSearch, 수집 기능을 하는 Logstash, 이를 시각화하는 도구인 Kibana의 앞글자만 딴 단어이다.

 

1) ElasticSearch

  • ElasticSearch는 분산 검색엔진으로, Logstash를 통해 수신된 데이터를 저장소에 저장하는 역할을 담당한다.
  • 데이터를 중심부에 저장하여 예상되는 항목을 검색하고 예상치 못한 항목을 밝혀낼 수 있다.
  • 정형, 비정형, 위치정보, 메트릭 등 원하는 방법으로 다양한 유형의 검색을 수행하고 결합할 수 있다.

 

2) Logstash

  • 오픈소스 서버 측 데이터 처리 파이프라인으로, 다양한 소스에서 동시에 데이터를 수집하고 변환하여 stash 보관소로 보낸다.
  • 수집할 로그를 선정해서, 지정된 대상 서버(ElasticSearch)에 인덱싱하여 전송하는 역할을 담당하는 소프트웨어이다.


3) Kibana

  • 데이터를 시각적으로 탐색하고 실시간으로 분석할 수 있다.
  • 시각화를 담당하는 HTML + Javascript 엔진이라고 보면 된다.

그리고 여기서 Beats를 추가하면 ELK Stack라고 한다.

Beats에도 종류가 있는데 FileBeat, PacketBeat, MetricBeat, WinlogBeat 등등 여러 가지 Beat가 있다.

Logstash로 이벤트 정보를 수집하기 위해선 실제 서비스 호스트에 수집기를 설치해야 하는데

Logstash는 다양한 필터와 설정을 제공해 무겁기 때문에 더 경량화된 수집기를 beats라고 할 수 있다.

 

그리고 여러 서버에서 많은 트래픽으로 로그를 보내면

ElasticSearch와 Logstash에서 부하를 받을 수 있는데 이는 Kafka를 사용해서 로그 데이터들을 E, L에서는 부하를 받지 않도록 좀 더 개선할 수 있다.

 

최종적인 모습은 다음과 같다.