상세 컨텐츠

본문 제목

[SPRING] 스프링 핵심 원리 고급편 | 프로젝트 생성과 로그 추적기

JAVA/기본 & 강의복습

by ranlan 2021. 11. 15. 23:57

본문

728x90

[인프런 강의] 스프링 핵심원리 고급편 (김영한 강사님)

 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강

www.inflearn.com

[GIT] https://github.com/ijo0r98/jpa-advanced

 

GitHub - ijo0r98/jpa-advanced

Contribute to ijo0r98/jpa-advanced development by creating an account on GitHub.

github.com

 


 

 

프로젝트 생성

스프링 프로젝트 생성은 항상 여기서 시작 https://start.spring.io

 

 

 

로깅 클래스

🗂 trace > 📄 TraceId

import java.util.UUID;

public class TraceId {

    private String id;
    private int level;

    public TraceId() {
        this.id = createId();
        this.level = 0;
    }

    private TraceId(String id, int level) {
        this.id = id;
        this.level = level;
    }

    private String createId() {
        return UUID.randomUUID().toString().substring(0, 8); // 유일한 식별자 생성
    }

    public TraceId createNextId() {
        return new TraceId(id, level + 1); // 레벨 증가, 아이디 동일
    }

    public TraceId previousId() {
        return new TraceId(id, level - 1); // 레벨 감소, 아이디 동일
    }

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

    public String getId() { return id; }

    public int getLevel() { return level; }
}

🗂 trace > 📄 TraceStatus

public class TraceStatus {

    private TraceId traceId;
    private Long startTimeMs; // 종료 시 시작 시간과 계산하여 시간 계산
    private String message;

    public TraceStatus(TraceId traceId, Long startTimeMs, String message) {
        this.traceId = traceId;
        this.startTimeMs = startTimeMs;
        this.message = message;
    }

    public TraceId getTraceId() { return traceId; }

    public Long getStartTimeMs() { return startTimeMs; }

    public String getMessage() { return message; }
}

 

 

로그 추적기 V1

🗂 trace > 🗂 hellotrace > 📄 HelloTraceV1

@Slf4j
@Component
public class HelloTraceV1 {

    // 레벨 표시를 위한 PREFIX
    private static final String START_PREFIX = "-->";
    private static final String COMPLETE_PREFIX = "<--";
    private static final String EX_PREFIX = "<X-";

    ...
}
  • @Component 
    개발자가 직접 작성한 클래스를 spring bean으로 등록
  • @Bean
    개발자가 직접 제어가 불가능한 외부 라이브러리등을 bean으로 등록하고자할 때 사용
  • 싱글턴 패턴(Singleton pattern)
    생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이며 최초 생성 이후에 호출된 생성자는 최초 생성된 객체를 리턴
    → 매번 새로운 객체 생성 시 발생되는 메모리 낭비 문제 해결

 

로깅 시작시 호출

public TraceStatus begin(String message) { 
    TraceId traceId = new TraceId(); // 새로운 traceId 생성
    Long startTimeMs = System.currentTimeMillis(); // 현재 로깅 시작 시점
    log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
    return new TraceStatus(traceId, startTimeMs, message);
}

정상 종료와 예외 발생 시 종료시 호출

// 정상 종료
public void end(TraceStatus status) {
    complete(status, null);
}

// 예외 종료
public void exception(TraceStatus status, Exception e) {
    complete(status, e);
}

종료 로깅

private void complete(TraceStatus status, Exception e) {
    Long stopTimeMs = System.currentTimeMillis();
    long resultTimeMs = stopTimeMs - status.getStartTimeMs();
    TraceId traceId = status.getTraceId();
    if (e == null) {
        log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs);
    } else {
        log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString());
    }
}

레벨에 따른 화살표 표시

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();
}

 

 

컨트롤러 작성

🗂 app > 🗂 v1 > 📄 OrderControllerV1

@RestController // @Controller + @ResponseBody
@RequiredArgsConstructor
public class OrderControllerV1 {

    private final OrderServiceV1 orderService;
    private final HelloTraceV1 trace; // @Component -> 스프링 빈 자동 등록

    @GetMapping("/v1/request")
    public String request(String itemId) {

        TraceStatus status = null;
        try {
            status = trace.begin("OrderController.request()"); // 로깅 시작
            orderService.orderItem(itemId);
            trace.end(status); // 로깅 종료
            return "ok"; 
        } catch (Exception e) {
            trace.exception(status, e);
            throw e; // 예외를 꼭 다시 던져줘야함
        }
    }
}

@RequiredArgsConstructor 

초기화 되지않은 final 필드나 @NonNull이 붙은 필드에 대해 생성자를 생성, 해당 어노테이션을 표기하지 않는 경우 아래와 같이 직접 빈 주입 작성

public OrderService(OrderRepositoryV0 orderRepository) {
    this.orderRepository = orderRepository;
}

 

서비스 작성

🗂 app > 🗂 v1 > 📄 OrderServiceV1

@Service
@RequiredArgsConstructor
public class OrderServiceV1 {

    private final OrderRepositoryV1 orderRepository;
    private final HelloTraceV1 trace;

    public void orderItem(String itemId) {
        TraceStatus status = null;
        try {
            status = trace.begin("OrderService.orderItem()"); // 로깅 시작
            orderRepository.save(itemId);
            trace.end(status); // 로깅 종료
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }
}

 

레퍼지토리 작성

🗂 app > 🗂 v1 > 📄 OrderRepositoryV1

@Repository
@RequiredArgsConstructor
public class OrderRepositoryV1 {

    private final HelloTraceV1 trace;

    public void save(String itemId) {

        TraceStatus status = null;
        try {
            status = trace.begin("OrderRepository.save()"); // 로깅 시작
            // 저장 로직 수행
            if (itemId.equals("ex")) {
                throw new IllegalStateException(("예외 발생!"));
            }
            sleep(1000); // 1초간 멈춤
            trace.end(status); // 로깅 종료
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }

    private void sleep(int m) {
        try {
            Thread.sleep(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

실행 결과

/v1/request?itemId=hello

 

정상 종료

/v1/request?itemId=ex

 

예외 종료

 

** traceId가 전달되지 않고 각자 생성되어 같은 요청, 응답에 대해 각각 다른 아이디가 부여되고 있음

** 마찬가지로 레벨 또한 전달되지 않아 새로 0으로 계속 생성되어 요청과 응답의 레벨이 표현되지 않고 있음

 

 

로그 추적기 V2

🗂 trace > 🗂 hellotrace > 📄 HelloTraceV2

처음 로깅 클래스를 만들어 traceId와 로깅 시작 시간을 지정하는 메서드와 이전 traceId를 이어받아 시작하는 메서드 구분

// v2 추가 (아이디 생성 후 다음 레벨에 아이디 전달)
public TraceStatus beginSync(TraceId beforeTraceId, String message) {
    TraceId nextId = beforeTraceId.createNextId(); // 아이디 동일, 레벨만 증가
    Long startTimeMs = System.currentTimeMillis();
    log.info("[{}] {}{}", nextId.getId(), addSpace(START_PREFIX, nextId.getLevel()), message);
    return new TraceStatus(nextId, startTimeMs, message);
}

 

컨트롤러 작성

🗂 app > 🗂 v1 > 📄 OrderControllerV2

@GetMapping("/v2/request")
public String request(String itemId) {

    TraceStatus status = null;
    try {
        status = trace.begin("OrderController.request()");
        orderService.orderItem(status.getTraceId(), itemId);
        trace.end(status);
        return "ok";
    } catch (Exception e) {
        trace.exception(status, e);
        throw e; 
    }
}

요청을 받는 첫 시작임으로 begin 메서드 실행하여 로깅 클래스 초기화 > 다음 레벨인 서비스에 생성한 status의 traceId를 전달

 

서비스 작성

🗂 app > 🗂 v1 > 📄 OrderServiceV2

public void orderItem(TraceId traceId, String itemId) {
    TraceStatus status = null;
    try {
        status = trace.beginSync(traceId, "OrderService.orderItem()");
        orderRepository.save(itemId, status.getTraceId());
        trace.end(status);
    } catch (Exception e) {
        trace.exception(status, e);
        throw e; 
    }
}

이전 레벨에서 전달받은 traceId를 받아 beginSync 메서드 실행 > 아이디는 유지, 레벨만 증가

 

레퍼지토리 작성

🗂 app > 🗂 v1 > 📄 OrderRepositoryV2

public void save(String itemId, TraceId traceId) {

    TraceStatus status = null;
    try {
        status = trace.beginSync(traceId, "OrderRepository.save()");
        // 저장 로직 수행
        if (itemId.equals("ex")) {
            throw new IllegalStateException(("예외 발생!"));
        }
        sleep(1000);
        trace.end(status);
    } catch (Exception e) {
        trace.exception(status, e);
        throw e; 
    }
}

서비스와 마찬가지로 이전 레벨의 traceId는 유지하고 레벨만 증가하는 beginSync 실행

 

실행 결과

/v2/request?itemId=hello

정상 종료

/v2/request?itemId=ex

예외 종료

 

한 요청에 대한 traceId가 잘 유지되고 레벨에 따른 로깅이 잘 됨을 확인할 수 있음

 

 

728x90

관련글 더보기

댓글 영역