ImageCPObject::uploadFile | path: {
RCA: ImageCPObject::uploadFile | path module logged instead of file path
Error Log#
ImageCPObject::uploadFile | path: {
default: [Object],
resolve: [Getter],
normalize: [Getter],
isAbsolute: [Getter],
join: [Getter],
relative: [Getter],
toNamespacedPath: [Getter],
dirname: [Getter],
basename: [Getter],
extname: [Getter],
format: [Getter],
parse: [Getter],
matchesGlob: [Getter],
sep: [Getter],
delimiter: [Getter],
win32: [Getter],
posix: [Getter],
_makeLong: [Getter]
}, error: {"errno":-110,"code":"ETIMEDOUT","syscall":"read"}
Impact#
- Service:
cupixworks-any-thumbnail-agent - Team: hassan-allam
- 발생 횟수: 1
- 최초 발생: 2026-04-09T10:55:13.142Z
- 최근 발생: 2026-04-09T10:55:13.142Z
Root Cause Summary#
이 에러에는 두 가지 문제가 결합되어 있습니다. 첫째, S3 cover 이미지 업로드 중 네트워크 타임아웃(ETIMEDOUT, errno -110, syscall read)이 발생했습니다. 이는 uploadCover → uploadFile → request.put 호출 과정에서 S3 presigned URL로의 HTTP PUT 요청이 TLS 읽기 단계에서 타임아웃된 것입니다. 둘째, uploadFile 메서드의 에러 핸들러(.on('error') 및 .on('response'))에서 파일 경로를 로깅할 때 함수 파라미터 filePath 대신 파일 상단에 import * as path from 'node:path'로 임포트된 Node.js path 모듈 객체를 참조하는 버그가 있습니다. 이로 인해 에러 로그에 실제 파일 경로 대신 path 모듈의 프로퍼티 목록이 출력되어 디버깅이 어려워졌습니다.
Technical Analysis#
Code Path#
- Entry point:
thumbnail-service.ts:87—supportModel.uploadCover()호출 image-cpobject.ts:264-281—uploadCover메서드가createCoverUploadUrl()로 S3 presigned URL을 생성한 후uploadFile()을 호출image-cpobject.ts:93-117—uploadFile메서드에서request.put()으로 S3에 파일 업로드 시도- Failure point:
image-cpobject.ts:113-114—.on('error')핸들러에서 ETIMEDOUT 에러 캐치
실행 흐름:
ThumbnailService::run에서 Asset 모델(key:s4yhjgpf62ma)에 대해 thumbnail 생성 후 cover 생성 흐름 진입:
// thumbnail-service.ts:83-88
if (supportModel.needCover) {
await supportModel.downloadOriginal();
await supportModel.preprocess();
await supportModel.generateCover();
await supportModel.uploadCover(); // 여기서 에러 발생
}
uploadCover가createCoverUploadUrl()로 S3 presigned URL을 획득한 후uploadFile()을 호출:
// image-cpobject.ts:264-281
uploadCover = async (): Promise<void> => {
// ...
await this.createCoverUploadUrl();
if (this._uploadCoverUrl == undefined || this._coverFilePath == undefined) {
// ...
return;
}
await this.uploadFile(this._uploadCoverUrl, this._coverFilePath, {
'Content-Type':'image/jpeg'
});
// ...
};
uploadFile메서드에서request.put()으로 S3에 파일을 업로드하는데, TLS 읽기 단계에서 타임아웃 발생. 에러 핸들러에서path(Node.js 모듈) 를filePath(함수 파라미터) 대신 사용하는 버그가 있음:
// image-cpobject.ts:93-117
private uploadFile = (url: string, filePath: string, headers: any): Promise<void> => new Promise((resolve, reject) => {
// ...
sendReq
.on('response', res => {
if (res.statusCode !== 200) {
logger.error('ImageCPObject::uploadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
// ^^^^ 버그: filePath가 아닌 path 모듈
reject(cupixAuth.handleError(res));
}
})
.on('error', err => {
logger.error('ImageCPObject::uploadFile | path: %s, error: %s', path, JSON.stringify(err));
// ^^^^ 버그: filePath가 아닌 path 모듈
reject(cupixAuth.handleError(err));
});
});
기대 동작: 에러 발생 시 filePath 변수(예: /tmp/workspace/s4yhjgpf62ma/s4yhjgpf62ma_cover.jpg)가 로그에 출력되어야 함.
실제 동작: path 모듈 객체(import * as path from 'node:path', line 2)가 출력되어 { default: [Object], resolve: [Getter], ... } 형태의 무의미한 로그가 남음.
참고로, 같은 파일의 downloadFile 메서드(line 83)는 동일한 패턴에서 filePath를 올바르게 사용하고 있어, uploadFile의 path 사용은 명백한 타이핑 실수입니다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-thumbnail-agent status:error "ImageCPObject"
Time range: 2026-04-09T09:55:00Z to 2026-04-09T11:55:00Z
service:cupixworks-any-thumbnail-agent
Time range: 2026-04-09T10:50:00Z to 2026-04-09T11:00:00Z
타임라인 (2026-04-09, KST):
| 시간 | 이벤트 |
|---|---|
| 19:52:40 | BaseService::runByMessage | id: NaN — SQS 메시지 수신, Asset 처리 시작 |
| 19:52:40 | ThumbnailService::newModel | model id: NaN, type: Asset — CPAsset 모델 생성 |
| 19:52:40 | CupixAuth::setSession — 인증 세션 설정 |
| 19:52:40 | ImageCPObject::downloadOriginal — 원본 이미지 다운로드 (thumbnail용) |
| 19:52:40 | ImageCPObject::generateThumbnail | begin |
| 19:52:42 | ImageCPObject::generateThumbnail | end — 1492ms 소요 |
| 19:52:42 | ImageCPObject::downloadOriginal — 원본 이미지 다운로드 (cover용) |
| 19:52:42 | ImageCPObject::generateCover | begin |
| 19:52:45 | ImageCPObject::generateCover | end — 3232ms, 848738 bytes |
| 19:52:46 | SQS 메시지 삭제 (이전 작업 완료) |
| 19:55:13 | ETIMEDOUT 에러 발생 — cover 업로드 중 타임아웃 |
에러 로그 원문:
{
"message": "CupixAuth::handleError | Undefined response: {\"stack\":\"Error: read ETIMEDOUT\\n at TLSWrap.onStreamRead (node:internal/stream_base_commons:218:20)\\n at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17)\",\"message\":\"read ETIMEDOUT\",\"errno\":-110,\"code\":\"ETIMEDOUT\",\"syscall\":\"read\"}"
}
{
"message": "BaseService::handlingMessageErrors | Error and message object - {\"error\":{\"errno\":-110,\"code\":\"ETIMEDOUT\",\"syscall\":\"read\"},\"sqsMessage\":{\"MessageId\":\"61d03ebd-9499-4634-ab52-276fba55406b\",\"Attributes\":{\"ApproximateReceiveCount\":\"1\"}}}"
}
타임아웃은 cover 생성 완료(19:52:45) 후 약 2분 28초 후에 발생했으며, 이는 S3 presigned URL로의 PUT 업로드 중 네트워크 레벨 타임아웃(TLSWrap.onStreamRead)을 나타냅니다.
Fix Recommendation#
즉시 조치 (Critical)#
파일: packages/cupix-tesla-thumbnail-agent/src/model/common/image-cpobject.ts
- Line 109:
path→filePath로 변경..on('response')핸들러의 에러 로그에서 Node.jspath모듈 대신 함수 파라미터filePath를 사용해야 합니다. - Line 114:
path→filePath로 변경..on('error')핸들러도 동일한 수정이 필요합니다.
이 두 줄만 수정하면 에러 발생 시 실제 파일 경로가 로그에 출력되어 디버깅이 가능해집니다.
단기 개선 (1주 이내)#
uploadFile메서드에 타임아웃 설정 추가를 검토. 현재request.put()에 타임아웃 옵션이 없어 S3 업로드가 무한정 대기할 수 있습니다.request라이브러리의timeout옵션을 설정하여 명시적으로 타임아웃을 제어하는 것이 바람직합니다.uploadFile에 재시도 로직 추가 검토. 일시적인 네트워크 타임아웃은 재시도로 해결될 수 있으며,CupixAuth.retryable()패턴이 이미 코드베이스에 존재합니다.
장기 개선 (재발 방지)#
request라이브러리는 deprecated 상태입니다.axios,node-fetch, 또는 Node.js 내장fetch로 마이그레이션을 검토하여 더 나은 타임아웃/재시도 지원과 TypeScript 타입 안전성을 확보할 수 있습니다.- ESLint의
no-shadow규칙 활성화를 검토.path모듈 임포트와 동명의 변수 사용으로 인한 혼동을 방지할 수 있습니다.
Monitoring#
- cover 업로드 타임아웃 빈도 추적:
service:cupixworks-any-thumbnail-agent status:error "uploadFile" "ETIMEDOUT"
- cover 업로드 실패 전반 모니터링:
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
- S3 업로드 지연 시간 메트릭 추가 검토 (custom metric으로
uploadFileelapsed time 기록)
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial — 두 줄의 변수명 수정(
path→filePath)으로 로깅 버그 해결. 네트워크 타임아웃 자체는 일시적 현상으로 발생 빈도 1회.