## 요구사항
0. 다음 흐름의 api를 구현한다.
client 요청 -> controller -> service -> repository -> service -> controller -> 응답
1. 각 계층에서 로그를 찍는다.
2. 각 계층에서 로직을 마칠 때 찍는 로그에는 소요 시간도 추가한다.
3. 어느 뎁스인지 UI로 표시한다.
4. 각 요청의 ID를 부과하여 같이 출력한다.
[fbda3e10] OrderController.request()
[fbda3e10] |--->OrderService.request()
[fbda3e10] | |--->OrderRepository.request()
[fbda3e10] | |<---OrderRepository.request() time=10001ms
[fbda3e10] |<---OrderService.request() time=10005ms
## [v0] 요구사항 0번 만족
단순 api 구현
## [v1] 요구사항 0, 1, 2번 만족
- 각 비즈니스 메서드 시작과 끝 지점에 trace.begin()/trace.end() 메서드를 추가하여 로그 출력
- begin()에서 currentTimeMillis()를 저장하고 end()에서 현재 시각과 차이를 계산하여 소요 시간 출력
## [v2] 요구사항 0, 1, 2, 3, 4번 만족
traceId를 파라미터로 넘겨 ID와 뎁스 정보를 유지함
ex) Service 비즈니스 메서드
public void orderItem(TraceId traceId, String itemId) { //파라미터 추가로 받음
TraceStatus status = null;
try {
status = trace.beginSync(traceId, "OrderService.request()"); //begin이 아닌 뎁스+1 로직이 포함된 beginSync() 사용
orderRepository.save(status.getTraceId(), itemId); //respository에 파라미터 넘김
trace.end(status);
} catch (Exception e){
trace.exception(status, e);
throw e; //로그는 예외에 영향 미치면 안 됨. 예외를 꼭 다시 던져줘야 함.
}
}
### 장점
- 정보를 넘겨줌으로 3, 4번 조건을 만족함
### 단점
- 파라미터로 넘기고 있어, 로그 때문에 비즈니스 메서드를 수정해주어야 함 (만약 인터페이스 구현체라면 인터페이스까지 수정해야 함)
- 시작점인 controller에서는 0뎁스에서 시작하고, 이후 계층에서는 뎁스 정보를 받아 +1해주는 로직이 포함되어, 단위 테스트 시 깔끔하지 않음
## [v3] 파라미터 방식 대신 Log 객체를 Spring Bean으로 등록
### 방법 1. 싱글톤 객체를 사용하여 TraceId 공유
@Slf4j
@Component
public class FieldLogTrace implements LogTrace {
...
private TraceId traceIdHolder; //traceId 동기화, 동시성 이슈 발생
...
}
#### 장점
- 파라미터 방식을 사용하여 발생하는 위 단점 모두 해결
#### 단점
- 싱글톤 객체로서 많은 요청 유입 시 데이터가 꼬이는 동시성 이슈 발생
### 방법 2. ThreadLocal을 사용하여 스레드별 데이터 구분
스레드 로컬이란?
해당 스레드만 접근할 수 있는 특별한 저장소를 뜻함.
멀티스래딩의 패턴으로 가지고 있는 개념이고 자바에서는 이를 지원하기 위해 java.lang.ThreadLocal클래스 제공
@Slf4j
@Component
public class ThreadLocalLogTrace implements LogTrace {
...
private ThreadLocal<TraceId> traceIdHolder = new ThreadLocal<>();
...
}
#### 장점
- 스레드별 동시성 이슈 해결
#### 주의점
- 데이터 사용을 마치고 나면 반드시 ThreadLocal.remove()를 호출해 값을 제거해야 함.
- Tomcat은 스레드풀을 사용하고 있기 때문에, 다음 요청에서 이전 요청의 데이터를 사용하면 데이터 유출 이슈로 이어질 수 있음
## 번외
스레드 테스트를 진행하며 "테스트하고 있는 스레드가 종료되기 전에 메인 스레드가 종료되어 로그를 찍을 수 없는 문제"를 마주했다.
강의에서 sleep을 걸어서 메인 스레드가 종료되는 시점을 늦추는 방법을 사용했지만, 다른 좋은 방법이 있다고 언급만 해주셨다.
바로 CatchDownLatch이다.
정리하기엔 너무 길어서 다른 글 가져왔다.
https://codechacha.com/ko/java-countdownlatch/
Java - CountDownLatch 사용 방법
CountDownLatch는 어떤 쓰레드가 다른 쓰레드에서 작업이 완료될 때 까지 기다릴 수 있도록 해주는 클래스입니다. countDown()이 호출되면 Latch의 수가 1개씩 감소하며, await()은 Latch의 숫자가 0이 될 때
codechacha.com