FileSharer 개발 노트 8 — ‘다운로드 완료’ 로그가 실제로 뜻하는 것

FileSharer 개발 노트 · 08 / 10

세 개발자가 사건 카드를 따라 전송이 어느 단계에서 멈췄는지 살펴봅니다.
AI 일러스트 · 세 개발자가 사건 카드를 따라 전송이 어느 단계에서 멈췄는지 살펴봅니다.

상대가 파일을 못 받았다고 했을 때 “내 화면에는 완료라고 나온다”는 답은 문제를 해결하지 못합니다. 그 완료가 요청 접수, 서버 쓰기 종료, 수신자 저장 중 어느 단계의 완료인지 알아야 합니다.

AccessLogger의 파일·화면 전달 흐름을 그린 설명도. 여기에 사용한 주소와 파일명은 예시이며 원본 access_log.txt는 읽지 않았습니다.
AccessLogger의 파일·화면 전달 흐름을 그린 설명도. 여기에 사용한 주소와 파일명은 예시이며 원본 access_log.txt는 읽지 않았습니다.

FileSharer는 공유 페이지 열람, 파일 다운로드, ZIP 전송, PIN 실패, 중단, 터널 재연결을 서로 다른 동작으로 기록합니다. 이 글에서는 운영 기록을 열어 보는 대신, 기록을 만드는 소스를 읽어 각 필드와 이벤트의 의미를 확인했습니다.

한 이벤트를 파일과 화면에 함께 보낸다

AccessLogger.Log는 먼저 AccessLogEntry를 만듭니다. 시각, 표시용 IP, 호스트 이름, 실제 소켓 IP, 동작, 대상, 공유 ID, User-Agent, 상세 설명이 들어갑니다. 그다음 파일에 추가하고 EntryLogged 이벤트로 화면에 알립니다.

이 구조 덕분에 파일 기록과 화면이 같은 이벤트 모델을 사용할 수 있습니다. 다만 파일 저장은 내부에서 예외를 잡고 반환합니다. 파일 쓰기에 실패해도 화면 이벤트가 호출될 수 있으므로, 화면에 보인다는 사실이 디스크 저장 성공까지 보장하지는 않습니다. 저장 실패를 별도 상태로 드러내는 것은 개선할 수 있는 부분입니다.

탭 구분 형식으로 기록하기 전에 Clean은 탭과 줄바꿈을 공백으로 바꿉니다. 대상 이름에 줄바꿈이 들어갔다는 이유로 이벤트 한 개가 여러 줄처럼 보이는 일을 줄이는 처리입니다. 이것이 HTML·스프레드시트 등 모든 출력 형식에 대한 검증을 대신하는 것은 아닙니다.

IP 두 개를 보관하는 이유

프록시를 거친 요청에서는 TCP 소켓의 상대와 실제 이용자로 표시하고 싶은 주소가 다를 수 있습니다. 코드에는 Ip와 SocketIp가 모두 있습니다. 이 둘을 보존하면 어느 단계에서 해석된 값인지 추적하기 쉽습니다.

원본 ResolveClientIp를 샘플 헤더로 실행한 결과는 다음과 같습니다. 아래 주소는 문서용 예시입니다.

입력 반환값
X-Forwarded-For: 192.0.2.10, 192.0.2.11 192.0.2.10
전달 헤더 없음, 소켓 IP 127.0.0.1 127.0.0.1

함수는 첫 전달 주소를 우선하지만, 전달 헤더를 신뢰할 프록시에서 받은 요청인지 자체적으로 검사하지 않습니다. 따라서 반환값을 검증된 사용자 신원이라고 쓰면 안 됩니다. 알려진 중계 경로에서만 전달 헤더를 신뢰하도록 경계를 두고, 그 밖에는 소켓 주소를 사용하는 설계가 필요합니다.

호스트 이름 조회는 다운로드 요청을 붙잡지 않도록 백그라운드에서 처리합니다. 첫 이벤트에는 빈 값이 들어갈 수 있고, 뒤의 이벤트에서는 캐시된 이름을 사용할 수 있습니다. IP와 이름을 단순히 한 사람의 신원으로 연결하는 데도 주의가 필요합니다.

서버의 쓰기 종료와 수신자의 저장은 다르다

ServeFile은 파일을 읽어 스트림에 쓴 뒤 보낸 바이트 수를 누적합니다. 반복문 뒤에는 완료 이벤트를, 예외가 발생하면 중단 이벤트를 남깁니다. 부분 요청이면 해당 구간을 보냈다는 설명도 붙입니다.

여기서 완료는 서버 전송 루프의 관찰입니다. 수신자가 디스크에 저장을 마쳤는지, 다시 열 수 있는지, 해시가 일치하는지까지 알리는 확인 메시지는 아닙니다. 특히 원본 파일이 도중에 짧아지는 등의 상황에서 읽기 결과가 0이 되면 반복문이 끝나고 완료 로그에 도달할 수 있습니다. sent가 예정 길이와 같은지 검사하는 완료 조건을 추가하는 것은 후속 개선 사항입니다.

따라서 운영 화면에는 “서버 전송 종료”처럼 관찰한 사실을 쓰고, 별도 검증이 있을 때 “수신 확인”을 추가하는 편이 정확합니다. 이름이 명확하면 사용자 문의에서 서로 다른 단계를 같은 말로 부르는 일을 줄일 수 있습니다.

최근 기록만 보여도 파일 전체를 읽을 수 있다

LoadRecent(max)는 화면에 최근 일부 줄을 보여 주지만, 내부적으로는 ReadAllLines로 파일 전체를 읽은 뒤 뒤쪽 범위를 선택합니다. 기록이 커지면 최대 표시 행 수를 줄이는 것만으로 읽기 비용이 줄지는 않습니다.

장기 사용을 고려한다면 날짜·용량별 파일 분리, 보관 기간, 최근 구간만 읽는 방식이 검토 대상입니다. 이것들은 현재 구현에 모두 있는 기능이 아니라 소스를 바탕으로 도출한 개선 방향입니다.

좋은 로그는 사실과 해석을 구분한다

접속 IP, 파일명, 호스트 이름에는 개인이나 업무 정보가 섞일 수 있습니다. 이번 원고와 그림에는 실제 로그를 사용하지 않았습니다. 공개 예제는 인위적으로 만든 이벤트만으로도 처리 흐름을 충분히 설명할 수 있습니다.

FileSharer 로그에서 배울 점은 이벤트를 여러 단계로 나눈 구조입니다. 여기에 기록 성공 여부, 출처의 신뢰 수준, 완료 조건까지 정확하게 붙이면 장애를 설명하는 자료로 더 유용해집니다.

소스 근거
FileSharer.cs:100–158 AccessLogger.Log / TryGetHostName · FileSharer.cs:193–261 Clean / Append / LoadRecent · FileSharer.cs:5051–5064 ResolveClientIp · FileSharer.cs:5324–5379 전송 로그
본문의 개선안은 적용된 기능과 구분합니다.

댓글