요청 식별자로 흐름 연결
65분 안팎
학습 목표
앱과 접근 로그의 동일 요청을 연결합니다.
개념
한 요청의 응답과 로그를 대조하기
같은 순간에 여러 사람이 자료를 조회하면 시각과 경로만으로 어떤 응답이 어느 로그에 해당하는지 찾기 어렵습니다. 요청 식별자는 한 번의 요청 처리에 이름을 붙여 응답과 내부 관찰을 연결하는 값입니다. 이번 레슨은 요청을 받는 가장 바깥에서 ID를 만들고 성공·자료 없음·JSON 오류 결과 모두에 같은 값을 남기는 구현을 다룹니다.
이 앱은 앞 모듈의 createApp 함수를 유지하고 server.cjs의 HTTP 연결 부분을 확장합니다. 자료 저장과 조회 정책을 바꾸지 않아 통신 관찰 개선이 기존 기능을 망가뜨렸는지 테스트로 비교할 수 있습니다. 기능 코드 안의 각 return에 ID 생성을 넣으면 오류 경로를 빠뜨리기 쉽습니다. HTTP 래퍼가 요청 시작에 한 번 생성하고 공통 send에서 응답과 로그를 처리합니다.
식별자는 인증 정보가 아닙니다
기본 ID는 node:crypto의 randomUUID로 생성합니다. 메모리에 저장한 단순 순번은 프로세스를 다시 시작하거나 여러 인스턴스를 쓰면 겹칠 수 있습니다. UUID도 절대 충돌하지 않는다고 단정하지 않지만 이 실습의 서버 내부 관찰용 선택으로 사용합니다. ID가 있거나 형식이 맞다는 이유만으로 요청한 사람이 누구인지, 자료를 볼 권한이 있는지 판단하지 않습니다.
클라이언트가 보낸 x-request-id는 이번 정책에서 신뢰하지 않고 무시합니다. 외부 입력을 그대로 사용하면 같은 값 반복이나 긴 값으로 관찰이 왜곡될 수 있습니다. 외부 추적 번호가 필요해지는 환경에서는 신뢰할 게이트웨이와 검증 규칙을 별도로 정합니다. 여기서는 서버가 소유한 관찰 ID 하나를 응답 헤더로 알려 주는 방식으로 범위를 제한합니다.
정상 응답에서만 ID를 반환하면 정작 조사할 실패 응답이 연결되지 않습니다. send는 201·200·404뿐 아니라 400과 413에도 x-request-id를 넣습니다. JSON 파싱보다 먼저 ID를 만들기 때문에 본문이 { 하나여도 오류 응답과 로그에 연결값이 남습니다. ID를 만드는 위치가 실패 경로의 관측 가능성을 결정한다는 점을 테스트로 확인합니다.
로그 필드를 고정하기
로그 이벤트는 requestId, method, path, status 네 필드입니다. requestId는 서버가 생성한 값이고 method는 수신 메서드, path는 쿼리 없는 pathname, status는 보낸 HTTP 상태입니다. 문서 내용이나 인증 헤더, 쿼리 전체는 넣지 않습니다. 최소 필드를 정해 두면 관찰 기능을 추가하면서 민감한 본문이 우연히 로그로 복사되는 일을 줄일 수 있습니다.
pathname만 남겨도 경로 자체에 민감한 값이 들어가는 서비스는 추가 처리가 필요합니다. 이번 앱은 숫자 자료 id와 고정 경로를 사용합니다. “쿼리를 제거했으니 모든 로그가 안전하다”는 결론은 범위를 벗어납니다. 실제 서비스에서는 경로 템플릿, 사용자 식별 정보의 필요성, 보관 기간과 읽기 권한도 함께 정해야 합니다. 그 정책은 후속 비밀 관리 레슨에 이어집니다.
log 함수는 호출자가 주입합니다. 기본 실행은 아무 곳에도 이벤트를 저장하지 않는 함수이고 테스트는 배열에 넣어 대조합니다. 이 방식은 디스크 접근·권한 오류와 HTTP 기능을 분리합니다. 영속 로그 수집 성공을 주장하지 않으며 테스트 배열의 이벤트가 응답과 맞는지만 봅니다. 운영 로거를 연결할 때는 저장 실패와 backpressure 정책을 별도로 설계해야 합니다.
응답 전송 함수에 한 번 연결하기
send는 status와 body를 받아 JSON 응답을 작성하고 같은 status로 로그 이벤트를 만듭니다. 분기마다 다른 코드로 응답과 로그를 작성하지 않으므로 한 곳의 수정이 전체 결과에 적용됩니다. 요청 시작의 requestId를 클로저로 참조하여 다른 요청의 값으로 덮어쓰지 않습니다. 전역 currentId 하나를 두면 병렬 요청 사이에서 값이 섞일 수 있습니다.
res.end 뒤 로그를 만드는 이 예제는 서버가 응답을 작성한 관찰입니다. 클라이언트가 마지막 바이트까지 수신했다는 보장은 아닙니다. 실제 전달 완료나 중도 연결 종료를 구별하려면 finish·close와 수집 정책을 더 설계해야 합니다. 이번 테스트는 로컬 클라이언트가 end까지 받은 상태·헤더와 서버 배열을 대조하므로 그 테스트의 범위 안에서 일치를 확인합니다.
여러 요청을 동시에 보내도 각 요청 콜백은 자기 requestId를 보관합니다. 테스트는 Promise.all로 여덟 번의 GET을 만들고 응답 ID 집합 크기가 여덟인지 확인합니다. 로그와 응답이 항상 같은 배열 순서에 놓인다고 일반화하지 않습니다. 응답을 입력 순서로 모으는 기능과 서버 도착 순서는 별개이므로 실제 연결은 ID를 키로 찾아 대조합니다.
고정 데모와 실제 기본 정책
데모는 idFactory를 주입하여 demo-1, demo-2처럼 고정 순서를 만듭니다. 이것은 실행 결과를 재현하고 비교하기 위한 테스트 설정입니다. 일반 createServer 호출은 randomUUID를 사용합니다. 문서의 demo-1을 모든 요청에 반환하면 테스트가 기대하는 서로 다른 ID 조건이 깨집니다. 주입할 수 있는 설계와 고정 응답으로 바꾸는 실수를 구분합니다.
첫 테스트 묶음은 여섯 번의 순차 요청을 보냅니다. 생성 201, 조회 200, 자료 없음 404, JSON 오류 400, 빈 제목 400, 과한 본문 413을 대조하고 각 헤더가 대응하는 로그와 같은지 확인합니다. 두 번째 묶음은 클라이언트의 spoofed ID가 무시되는지, 쿼리 dummy가 로그에 없는지, 병렬 ID가 서로 다른지 확인합니다. 실패 사례의 로그가 빠지면 첫 묶음의 길이·대조 검사가 잡습니다.
starter의 차이를 좁혀 고치기
starter에서는 응답과 이벤트 ID가 missing으로 고정되어 있습니다. 생성된 requestId를 두 위치에 연결하고 bash check.sh를 다시 실행합니다. 기존 app.cjs나 HTTP 상태 기대값은 수정할 이유가 없습니다. 테스트가 “missing과 r-1이 다르다”라고 보여 주면 어떤 경로가 미연결인지 찾습니다. 응답만 고쳤는데 실패하면 로그 이벤트에도 같은 변수를 썼는지 봅니다.
로그 이벤트 개수가 요청보다 많으면 같은 요청에서 두 번 send했거나 별도 위치에서 추가 로그를 만들었는지 확인합니다. 개수가 작으면 오류 반환 경로가 공통 send를 지나지 않았는지 봅니다. 여러 요청의 ID가 같으면 idFactory를 반복 호출하지 않았거나 전역 값을 재사용했는지 찾습니다. ID 형식만 맞추는 테스트보다 실제 상관 관계와 서로 다른 요청의 구분을 검사하는 이유입니다.
출력에 쿼리 값이 보이면 new URL의 pathname 대신 req.url 전체를 기록했는지 살핍니다. 테스트는 이벤트 키 목록도 비교하므로 body 같은 필드 추가를 잡습니다. 디버깅 편의를 이유로 요청 객체 전체를 JSON으로 넣으면 순환 참조 오류뿐 아니라 내용 노출 위험도 생깁니다. 필요한 관찰 필드를 명시적으로 선택하고 새로운 필드에는 목적과 검사를 함께 추가합니다.
요청 흐름과 누적 미션
미션의 보고서는 HTTPS 논리 흐름인 DNS → TCP → TLS → HTTP와 이번 실제 루프백 HTTP 평문 시험을 나누어 적습니다. E01의 함수 관찰, E02의 Linux 실행 관찰, E03의 합성 통신 분류가 서로 다른 증거라는 점도 남깁니다. HTTP 식별자 테스트가 통과해도 실제 DNS·TLS 검증이나 로그인·인가 안전성이 증명되는 것은 아닙니다.
재시도로 같은 자료를 두 번 요청하면 두 요청 ID는 다릅니다. 하나의 사용자 작업을 여러 서비스에 걸쳐 연결하려면 별도의 trace 정책이 필요합니다. ID가 다르다고 서로 관련 없는 사건이라고 단정하지 않고 ID가 같다고 자격 증명으로 받아들이지 않습니다. 완료 시에는 오류 응답까지 연결한 코드 위치, 병렬 요청 구분 근거, 로그에 남기지 않은 내용을 함께 설명합니다.
따라하기
요청별 클로저 연결 모형
다른 ID를 가진 응답과 이벤트를 함수 모형에서 비교합니다. 실제 소켓 관찰은 다음 단계입니다.
function event(id,status){return {response:{id,status},log:{requestId:id,status}};}
for(const id of ['r-1','r-2']){const e=event(id,400);console.log(e.response.id===e.log.requestId,e.log.status);}실행 결과
true 400 true 400
실제 응답과 로그 연결 관찰
solution ZIP에서 실행합니다. 네 요청의 상태·응답 ID와 로그 개수 요약을 관찰합니다. 샌드박스의 listen EPERM으로 이 단계는 실행 검증 대기이며 출력은 비워 두었습니다. 밖에서 정상 종료와 201·200·404·400, 네 ID 연결을 확인합니다.
node http-demo.cjs병렬과 오류까지 검증
starter의 헤더와 이벤트 연결을 수정하고 실행합니다. 두 테스트 묶음 통과를 확인합니다. 순차 응답 6개와 병렬 요청 8개를 검사하며 서버는 테스트 완료 때 닫힙니다.
bash check.sh확인 문제
실습
server.cjs의 고정 missing 값을 요청 시작 때 만든 requestId로 바꿉니다. 응답 헤더와 로그 이벤트에 같은 값을 연결합니다. 기존 생성·조회 함수와 테스트 기대값은 유지합니다. 성공·자료 없음·형식 오류·본문 한도·병렬 요청 및 로그 최소화 검사를 모두 통과합니다.
루프백 listen 권한이 없는 샌드박스에서는 external 대기입니다. node --test handler.test.cjs로 메모리 스트림의 요청 처리 검사는 실행할 수 있지만 실제 HTTP 소켓 통과로 기록하지 않습니다.
실행 명령
bash check.sh
기대 결과
2개 HTTP 테스트 묶음 통과, 종료 코드 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 비밀값을 코드와 로그에서 보호하는 방법을 설명합니다.
- 요청 식별자가 인증 정보가 아닌 이유를 설명합니다.