[Logging] 로그 출력을 효율적으로 생성 및 추적하기 3편

2024. 12. 26. 03:05·Spring/Logging
728x90
반응형
SMALL

2024.12.25 - [Spring] - [Logging] 로그 출력을 효율적으로 생성 및 추적하기 1편

2024.12.25 - [Spring] - [Logging] 로그 출력을 효율적으로 생성 및 추적하기 2편

 

Logging Trace V3

V2에서는 TraceId의 동기화 문제는 해결했지만 모든 파라미터에 대한 수정의 필요성이 생겨 유지 보수에 어려움이 발생한다는 문제와 beginSync메서드를 반드시 수행해야 한다는 문제가 있다.

이러한 문제를 해결하기 위한 V3를 구현해보려 한다. 첫 번째 방법은 LogTrace라는 추상체로 중복되는 작업에 대한 메서드를 공통 모듈로 만들고 구현체를 사용하는 방법이다.

목차

  • TraceId 변경점
  • LogTrace
  • FieldLogTrace
  • 결과
  • 문제점

TraceId 변경점

createId

level의 값을 1증가시킨 새로운 TraceId 객체를 생성하지만 매개변수로 traceId를 받으므로 기존의 객체에서 값만 변경시킨것과 동일하다.

isFirstLevel

노드의 첫 단계인지 확인한다.

createPreviousId()

FieldLogTrace의 complete메서드에서 수행되는 메서드이다. 순회가 끝나고 값을 반환하는 과정에서 노드를 감소시키며 상위 단계로 돌아온다.


public TraceId createNextId() {
    return new TraceId(traceId, level + 1);
}

public boolean isFirstLevel() {
    return level == 0;
}

public TraceId createPreviousId() {
    return new TraceId(traceId, level - 1);
}

LogTrace

모든 로직에서 공통으로 수행하는 세가지 메서드를 추상화한다.


public interface LogTrace {
    TraceStatus begin(String msg);
    void end(TraceStatus status);
    void exception(TraceStatus status, Exception e);
}

FieldLogTrace

기존의 TraceV1, V2 코드와 다른 몇 가지가 있다. 아래에서 추가된 필드와 메서드를 하나씩 설명하겠다.


@Component
@Slf4j
public class FieldLogTrace implements LogTrace {
    private static final String START_PREFIX = "-->";
    private static final String END_PREFIX = "<--";
    private static final String EXCEPTION_PREFIX = "<X-";

    private TraceId traceIdHolder;

    @Override
    public TraceStatus begin(String msg) {
        syncTraceId();
        TraceId traceId = traceIdHolder;
        Long startTimeMills = currentTimeMillis();
        log.info("[{}] {}{}", traceId.getTraceId(), addSpace(START_PREFIX, traceId.getLevel()), msg);
        return new TraceStatus(traceId, startTimeMills, msg);
    }

    private static String addSpace(String prefix, int level) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < level; i++) {
            sb.append((i==level-1)? "|"+prefix : "| ");
        }
        return sb.toString();
    }

    private void syncTraceId() {
        if (traceIdHolder == null) {
            traceIdHolder = new TraceId();
        } else {
            traceIdHolder = traceIdHolder.createNextId();
        }
    }

    @Override
    public void end(TraceStatus status) {
        complete(status, null);
    }

    @Override
    public void exception(TraceStatus status, Exception e) {
        complete(status, e);
    }

    private void complete(TraceStatus status, Exception e) {
        Long stopTimeMs = currentTimeMillis();
        long resultTimeMs = stopTimeMs - status.getStartTime();
        TraceId traceId = status.getTraceId();

        if (e == null) {
            log.info("[{}] {}{} time={}ms", traceId.getTraceId(),
                    addSpace(END_PREFIX, traceId.getLevel()), status.getMessage(),
                    resultTimeMs);
        } else {
            log.info("[{}] {}{} time={}ms ex={}", traceId.getTraceId(),
                    addSpace(EXCEPTION_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs,
                    e.toString());
        }

        releaseTraceId();
    }

    private void releaseTraceId() {
        if (traceIdHolder.isFirstLevel()) {
            traceIdHolder = null; // 1 -> destroy
        } else {
            traceIdHolder = traceIdHolder.createPreviousId(); // 1 -> 2 -> 3(complete) -> 2 -> 1
        }
    }
}

추가 필드 traceHolder

TraceId 객체를 필드로 분리하여 메서드에서 호출하는 방식에서 변경되었다.

begin 메서드

syncTraceId() 메서드를 사용하여 시작과정에서 traceId의 동기화를 진행한다. 처음 호출되었다면 새로 생성된 level 0번의 아이디를 새로 부여하고 그게 아니라면 기존의 id값의 레벨을 증가시킨다.


@Override
public TraceStatus begin(String msg) {
    syncTraceId();
    TraceId traceId = traceIdHolder;
    Long startTimeMills = currentTimeMillis();
    log.info("[{}] {}{}", traceId.getTraceId(), addSpace(START_PREFIX, traceId.getLevel()), msg);
    return new TraceStatus(traceId, startTimeMills, msg);
}

end, Exception의 complete() 메서드

기존의 코드와 동일하지만 releaseTraceId 메서드가 추가되었다. 수행을 마친 작업에 대해 level을 감소시키며 첫 번째 노드로 돌아왔다면 traceIdHolder를 힙영역에서 소멸시킨다.


private void complete(TraceStatus status, Exception e) {
    Long stopTimeMs = currentTimeMillis();
    long resultTimeMs = stopTimeMs - status.getStartTime();
    TraceId traceId = status.getTraceId();

    if (e == null) {
        log.info("[{}] {}{} time={}ms", traceId.getTraceId(),
                addSpace(END_PREFIX, traceId.getLevel()), status.getMessage(),
                resultTimeMs);
    } else {
        log.info("[{}] {}{} time={}ms ex={}", traceId.getTraceId(),
                addSpace(EXCEPTION_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs,
                e.toString());
    }

    releaseTraceId();
}

private void releaseTraceId() {
    if (traceIdHolder.isFirstLevel()) {
        traceIdHolder = null; // 1 -> destroy
    } else {
        traceIdHolder = traceIdHolder.createPreviousId(); // 1 -> 2 -> 3(complete) -> 2 -> 1
    }
}

결과

기존의 V2를 추상체인 LogTace 객체로 변경 후 수행했더니 정상적으로 수행됐다.


2024-12-26T02:33:44.415+09:00  WARN 5391 --- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Retrograde clock change detected (housekeeper delta=29s565ms), soft-evicting connections from pool.
2024-12-26T02:48:16.604+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] UserController.create()
2024-12-26T02:48:16.605+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] |-->UserService.create()
2024-12-26T02:48:16.620+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] |<--UserService.create() time=15ms
2024-12-26T02:48:16.620+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] UserController.create() time=16ms

문제점

보다시피 5개의 쓰레드가 동작하고 각각의 고유 TraceID값을 보유하고 있다. 하지만 스프링부트에서는 싱글톤으로 객체를 생성하고 관리하기 때문에
traceHolder를 동시에 사용하다보니 값이 순서대로 작동하는게 아닌 어떤 쓰레드가 사용중인 값을 다른 쓰레드가 사용하다보니 얽히고 섥혀서 꼬이게 된것이다.
다시 말해 워낼 isFirstLevel이 true여야 하는 값이 이미 2,3과 같은 값을 갖고 있는 것이다.
다음에 해결해야 하는 문제는 동시성의 해결이였다. 이 과정은 V4로 작성하려고 한다.
: [75c0f349] UserController.create()
2024-12-26T02:48:16.605+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] |-->UserService.create()
2024-12-26T02:48:16.620+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] |<--UserService.create() time=15ms
2024-12-26T02:48:16.620+09:00  INFO 5391 --- [io-8080-exec-10] c.e.l.global.trace.FieldLogTrace         : [75c0f349] UserController.create() time=16ms
2024-12-26T02:51:15.256+09:00  INFO 5391 --- [nio-8080-exec-1] c.e.l.global.trace.FieldLogTrace         : [b6cefb85] UserController.create()
2024-12-26T02:51:15.256+09:00  INFO 5391 --- [nio-8080-exec-5] c.e.l.global.trace.FieldLogTrace         : [4aaa902f] UserController.create()
2024-12-26T02:51:15.256+09:00  INFO 5391 --- [nio-8080-exec-2] c.e.l.global.trace.FieldLogTrace         : [6e8209cb] UserController.create()
2024-12-26T02:51:15.256+09:00  INFO 5391 --- [nio-8080-exec-3] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] UserController.create()
2024-12-26T02:51:15.257+09:00  INFO 5391 --- [nio-8080-exec-1] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] |-->UserService.create()
2024-12-26T02:51:15.257+09:00  INFO 5391 --- [nio-8080-exec-5] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | |-->UserService.create()
2024-12-26T02:51:15.257+09:00  INFO 5391 --- [nio-8080-exec-2] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | | |-->UserService.create()
2024-12-26T02:51:15.256+09:00  INFO 5391 --- [nio-8080-exec-6] c.e.l.global.trace.FieldLogTrace         : [ad99b3a2] UserController.create()
2024-12-26T02:51:15.257+09:00  INFO 5391 --- [nio-8080-exec-3] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | |-->UserService.create()
2024-12-26T02:51:15.257+09:00  INFO 5391 --- [nio-8080-exec-6] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | | | |-->UserService.create()
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-6] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | | | |<--UserService.create() time=18ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-6] c.e.l.global.trace.FieldLogTrace         : [ad99b3a2] UserController.create() time=19ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-3] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | |<--UserService.create() time=18ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-5] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | |<--UserService.create() time=18ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-3] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] UserController.create() time=19ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-5] c.e.l.global.trace.FieldLogTrace         : [4aaa902f] UserController.create() time=19ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-1] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] |<--UserService.create() time=18ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-2] c.e.l.global.trace.FieldLogTrace         : [4c0eb851] | | | |<--UserService.create() time=18ms
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-2] c.e.l.global.trace.FieldLogTrace         : [6e8209cb] UserController.create() time=19ms ex=java.lang.NullPointerException: Cannot invoke "com.example.loggingtest.global.trace.TraceId.isFirstLevel()" because "this.traceIdHolder" is null
2024-12-26T02:51:15.275+09:00  INFO 5391 --- [nio-8080-exec-1] c.e.l.global.trace.FieldLogTrace         : [b6cefb85] UserController.create() time=19ms ex=java.lang.NullPointerException: Cannot invoke "com.example.loggingtest.global.trace.TraceId.isFirstLevel()" because "this.traceIdHolder" is null
2024-12-26T02:51:15.277+09:00 ERROR 5391 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "com.example.loggingtest.global.trace.TraceId.isFirstLevel()" because "this.traceIdHolder" is null] with root cause

java.lang.NullPointerException: Cannot invoke "com.example.loggingtest.global.trace.TraceId.isFirstLevel()" because "this.traceIdHolder" is null

 

728x90
반응형
SMALL

'Spring > Logging' 카테고리의 다른 글

[Logging] 로그 출력을 효율적으로 생성 및 추적하기 6편 - Proxy  (1) 2025.01.06
[Logging] 로그 출력을 효율적으로 생성 및 추적하기 5편 - Template Method  (1) 2025.01.06
[Logging] 로그 출력을 효율적으로 생성 및 추적하기 4편 ThreadLocal사용기  (0) 2025.01.06
[Logging] 로그 출력을 효율적으로 생성 및 추적하기 2편  (0) 2024.12.25
[Logging] 로그 출력을 효율적으로 생성 및 추적하기 1편  (0) 2024.12.25
'Spring/Logging' 카테고리의 다른 글
  • [Logging] 로그 출력을 효율적으로 생성 및 추적하기 5편 - Template Method
  • [Logging] 로그 출력을 효율적으로 생성 및 추적하기 4편 ThreadLocal사용기
  • [Logging] 로그 출력을 효율적으로 생성 및 추적하기 2편
  • [Logging] 로그 출력을 효율적으로 생성 및 추적하기 1편
공부하고 기억하는 공간
공부하고 기억하는 공간
IT 비전공자로 시작하여 훌륭한 개발자가 되기 위해 공부하고 있는 공간입니다. 틀린 내용이나 부족한 부분이 있으면 댓글로 알려주세요 바로 수정하겠습니다.
    250x250
  • 공부하고 기억하는 공간
    IT - railroad
    공부하고 기억하는 공간
  • 전체
    오늘
    어제
    • 분류 전체보기 (325)
      • 면접 준비 (22)
        • OS (6)
        • Spring Security (0)
        • Java (3)
        • DB (11)
        • Network (3)
      • ElasticSearch (2)
      • Kafka (4)
      • Spring (22)
        • Spring Cloud (7)
        • Security6 (5)
        • JPA (12)
        • 프로젝트 리팩토링 회고록 (4)
        • Logging (8)
        • Batch (2)
      • Redis (17)
        • Redis 개념 (8)
        • Redis 채팅 (5)
        • Redis 읽기쓰기 전략 (1)
      • AWS (11)
      • 리눅스 (29)
        • 리눅스 마스터 2급 (5)
        • 네트워크(기초) (7)
        • 리눅스의 이해 (6)
        • 리눅스의 설치 (2)
        • 리눅스 운영 및 관리 (6)
      • JAVA-기초 (16)
        • JAVA기본 (11)
        • Design Pattern (5)
      • JSP (27)
        • JSP 기본 개념 (10)
        • JSP (1)
      • SQL (1)
      • TIL (36)
      • 문제 풀이 (2)
        • Programmers (9)
        • 백준 문제풀이 (28)
      • JavaScript (10)
      • HTML (17)
      • Ngrinder (1)
        • Ngrinder 문서 정리 (1)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      java
      Til
      redis
      리눅스마스터2급
      자바
      JavaScript
      HTML
      자바 면접
      스프링프레임워크
      자바 면접질문
      Spring Data Redis
      자바기초
      CSS
      자바스크립트
      자바 알고리즘
      백준
      JS
      springsecurity
      jsp기초
      redis 채팅
      자바 반복문
      Springframework
      리눅스
      Spring
      spring redis
      프로그래머스
      JSP
      레디스
      리눅스마스터2급정리
      jsp request
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    공부하고 기억하는 공간
    [Logging] 로그 출력을 효율적으로 생성 및 추적하기 3편
    상단으로

    티스토리툴바