[인프런 강의] 스프링 핵심원리 고급편 (김영한 강사님)
[GIT] https://github.com/ijo0r98/jpa-advanced
프로젝트 생성
스프링 프로젝트 생성은 항상 여기서 시작 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-";
...
}
로깅 시작시 호출
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가 잘 유지되고 레벨에 따른 로깅이 잘 됨을 확인할 수 있음
[SPRING] 스프링 핵심 원리 고급편 | 쓰레드 로컬 (0) | 2022.01.18 |
---|---|
[JAVA] String to Int, Int to String 형변환 (0) | 2022.01.10 |
[JAVA] 자바 자료형, Integer와 int의 차이에 대해 (0) | 2021.08.26 |
[JAVA] 객체의 생성과 표현 방법 (1) | 2021.08.23 |
[SPRING] Querydsl | 스프링 데이터 JPA로 페이징하기 (0) | 2021.05.18 |
댓글 영역