요청 ID와 오류 추적
60분 안팎
학습 목표
요청 ID·오류 코드·처리 시간으로 실패를 추적합니다.
개념
실패를 한 요청으로 좁힙니다
동아리 회원이 대여 버튼을 눌렀는데 오류가 났다고 알려 옵니다. 같은 시각에 여러 회원이 요청했다면 시간만으로는 해당 실패를 고르기 어렵습니다. 요청 ID는 응답과 서버 기록을 이어 주는 표식입니다. 회원에게 비밀번호나 쿠키 대신 응답 헤더 X-Request-ID를 알려 달라고 하면 민감한 자료 없이 조사할 출발점을 얻습니다. 이 레슨을 마치면 입력 오류와 DB 실패를 구별하고 같은 ID에 해당하는 완료 기록을 찾아 어떤 경계까지 처리했는지 설명할 수 있습니다.
이번 모듈의 작업 환경입니다
각 실습의 starter 압축을 서로 다른 폴더에 풉니다. JDK 17·Python 3·Bash와 포함된 Maven Wrapper를 사용하며 Java 의존성은 준비된 캐시로 확인합니다. 학습자는 ./mvnw test를 실행하고 작성 검증은 ./mvnw -o -q test로 진행합니다. Java는 Spring Boot 3.1.5, H2는 2.1.214로 고정했습니다. 앞 모듈의 도서 API와 누적 테스트를 보존했습니다. 테스트용 메모리 DB와 임시 파일 DB만 사용하며 devinkr 자체의 설정 파일을 읽지 않습니다. 이번 실습은 소켓 서버나 Maven 데몬을 시작하지 않습니다.
무엇을 기록할지 먼저 결정합니다
완료 로그에는 요청 ID, 메서드, 라우트 템플릿, 상태 코드, 오류 코드, 처리 밀리초를 둡니다. GET /books/7을 그대로 기록하기보다 /books/{id}를 기록하면 식별자를 줄이고 같은 종류의 요청을 묶을 수 있습니다. 쿼리 문자열·본문·Authorization·Cookie·DB URL은 제외합니다. 운영자가 보고 싶은 정보라고 해서 모든 입력을 보관하면 조회 편의보다 노출 위험과 저장 비용이 커집니다. 허용 필드만 만드는 방식은 나중에 특정 비밀 문자열을 지우는 방식보다 새로운 입력 종류에 덜 흔들립니다.
요청 ID도 입력입니다
클라이언트가 보낸 X-Request-ID를 아무 검사 없이 로그에 쓰면 줄바꿈으로 가짜 로그를 만들거나 매우 긴 값으로 저장량을 늘릴 수 있습니다. requestId는 영문·숫자·밑줄·하이픈으로 이루어진 1~64자만 받아들이고 나머지는 새 UUID로 바꿉니다. ID가 없을 때도 생성합니다. 이 규칙은 안전한 출력 경계를 위한 것이며 인증 수단은 아닙니다. 허용 형식의 ID를 두 요청에 재사용할 수도 있으므로 같은 ID 검색 뒤에는 시각·메서드·템플릿까지 함께 확인합니다. 신뢰하지 않는 외부 환경에서는 서버 ID를 항상 생성하는 정책도 검토합니다.
필터는 보안 필터보다 앞에 둡니다
RequestTraceFilter는 OncePerRequestFilter를 상속하고 높은 순서로 배치하여 로그인·인가 단계의 거절도 관찰합니다. 요청 속성에 ID를 넣고 응답 헤더에도 같은 값을 설정한 뒤 다음 필터로 진행합니다. 컨트롤러 안에서만 ID를 만들면 CSRF 거절처럼 컨트롤러에 도달하지 않는 실패가 빠집니다. 이 예제는 동기 Servlet 요청에 한정하며 async와 별도 error dispatch의 관측을 완성했다고 주장하지 않습니다. 스트리밍 응답이나 비동기 작업을 도입할 때는 완료 경계와 ID 전파를 새로 설계해야 합니다.
상태와 업무 오류 코드를 같이 읽습니다
400은 입력 형태의 문제를 뜻하고 INVALID_INPUT은 이 API의 구체적인 오류 분류입니다. 500과 INTERNAL_ERROR는 서버 내부 실패를 나타내지만 이것만으로 DB가 고장 났다고 확정할 수는 없습니다. TraceResponseAdvice가 ApiErrors.ErrorBody의 코드만 요청 속성에 옮깁니다. 기존 응답 JSON은 그대로 반환하므로 앞 단계에서 확인한 정확한 본문 계약이 유지됩니다. SecurityConfiguration의 거절 처리도 AUTH_REQUIRED·ACCESS_DENIED·LOGIN_FAILED를 속성에 남깁니다. 코드가 없으면 완료 로그는 HTTP 상태를 바탕으로 일반 분류를 사용합니다.
예외 메시지 대신 안전한 단서를 남깁니다
DB 예외 메시지에는 SQL·연결 문자열·사용자 입력이 포함될 수 있습니다. ApiErrors는 예외 클래스와 MDC의 요청 ID만 기록하고 사용자에게는 일반 메시지를 줍니다. 실습의 /db-fail은 없는 테이블을 조회하여 실제 JDBC 오류를 만들며 BadSqlGrammarException이라는 종류와 완료 로그의 ID를 연결합니다. 이 재현은 SQL 실행 실패를 확인하는 것이고 DB 연결 끊김을 재현한 것은 아닙니다. 필요하면 별도 테스트에서 연결 실패를 주입하고 동일한 비밀값 제외 원칙을 유지합니다.
MDC는 요청이 끝나면 정리합니다
MDC는 현재 실행 스레드에 로그 문맥을 붙이는 도구입니다. 스레드를 다음 요청에 재사용할 때 이전 ID가 남으면 다른 회원의 실패가 잘못 연결됩니다. 필터는 시작 전 값을 기억하고 finally에서 원래 값으로 돌리거나 제거합니다. 컨트롤러가 예외를 던져도 정리가 수행되도록 정상 경로 밖에 둡니다. 중첩된 호출의 문맥이 있었다면 단순히 clear로 모두 지우지 않고 requestId만 복원합니다. 작업을 다른 스레드로 넘기면 MDC가 자동 전파된다고 가정하지 않습니다.
처리 시간의 경계를 설명합니다
경과 시간은 System.nanoTime으로 시작과 끝의 차이를 구해 밀리초 정수로 바꿉니다. 달력 시각은 조사 시간대를 찾는 데 사용하고 같은 프로세스 안의 소요 시간과 구별합니다. 이 필터가 측정하는 범위는 다음 필터와 컨트롤러 처리까지입니다. 클라이언트의 이름 해석·연결·전체 응답 전송을 포함한 총 지연과 같지 않습니다. 아주 짧은 요청은 정수 밀리초로 0이 나올 수 있습니다. 0을 관측 누락으로 단정하지 말고 측정 단위와 절삭 규칙을 확인합니다.
테스트 실패에서 수정 위치를 찾습니다
starter는 요청 ID를 검사하지 않고 반환합니다. unsafeAndOversizedIdsAreReplaced가 실패하면 생성 규칙을 고칩니다. boundaryIdIsAccepted는 64자가 정상이라는 경계를 지킵니다. responseAndLogHaveSameIdAndRoute는 응답과 로그의 ID 일치, 템플릿 사용, 민감 입력 제외를 함께 검사합니다. validationErrorHasCodeAndMatchingId가 실패하면 오류 코드 전달을, previousMdcIsRestored가 실패하면 finally를 확인합니다. 테스트 자체를 삭제하거나 모든 요청에 고정 ID를 넣어 통과시키면 추적 목표를 잃습니다.
검색 결과를 사고 기록으로 바꿉니다
오류 신고의 ID를 찾은 뒤 메서드와 템플릿, 상태, 오류 종류, 걸린 시간을 표에 적습니다. 같은 회원의 다른 요청이나 재시도를 섞지 않습니다. 입력 오류는 회원이 고칠 정보와 서버 검증의 적절성을 보고, 내부 오류는 같은 ID의 예외 종류와 DB·배포 상태를 이어서 봅니다. 원인이 아직 확인되지 않았으면 관측 사실과 가설을 구분합니다. 요청 구간을 더 세분화하는 방법은 더 읽기에서 이어가며 이번 제출물은 안전한 완료 로그와 재현 테스트로 한정합니다.
따라하기
ID 경계의 실패를 읽습니다
starter에서 검사를 실행합니다. unsafeAndOversizedIdsAreReplaced의 기대 UUID와 실제 줄바꿈 ID를 대조합니다. 본문이나 헤더 전체를 로그에 찍어 진단하지 않습니다.
./mvnw test허용 입력과 대체 생성을 구현합니다
RequestTraceFilter.requestId의 TODO를 구현합니다. matches로 영문·숫자·밑줄·하이픈 1~64자를 허용하고 나머지는 UUID.randomUUID().toString()으로 반환합니다. finally의 문맥 복원은 유지합니다.
누적 검사를 확인합니다
solution에서 직접 실행한 Maven 결과를 보고서로 집계했습니다. 수정한 starter도 실패·오류·건너뜀 없이 169개인지 확인합니다.
python3 report.py실행 결과
Tests=169 Failures=0 Errors=0 Skipped=0
같은 ID로 오류 종류를 연결합니다
src/test/java/lab/RequestTraceTest.java를 열어 bad-input과 db-error 요청을 찾습니다. -Dtest 옵션으로 재실행하고 X-Request-ID, code, 예외 종류를 연결합니다. 시간값은 실행마다 달라 별도 고정 출력으로 제시하지 않습니다.
./mvnw -Dtest=RequestTraceTest test민감 자료 제외 근거를 제출합니다
responseAndLogHaveSameIdAndRoute의 금지 입력 검사와 RequestTraceFilter의 허용 로그 필드를 대조합니다. UUID가 인증정보가 아니며 동기 필터 시간은 클라이언트 전체 지연과 다르다는 제한을 한 문장씩 기록합니다.
확인 문제
실습
RequestTraceFilter.requestId의 허용 문자·길이 TODO를 구현합니다. 안전한 ID 대체, 응답과 로그의 ID 일치, 검증 오류·실제 SQL 실패, MDC 정리와 민감 입력 제외를 누적 테스트로 확인합니다. report.py 집계와 두 오류의 추적 설명을 제출합니다.
실행 명령
./mvnw test
기대 결과
Tests=169, Failures=0, Errors=0, Skipped=0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 실행 환경에서 API 오류를 추적하는 순서를 설명합니다.