ES /docs

ImageCPObject::uploadFile | path: {

RCA: ImageCPObject::uploadFile | path module logged instead of file path

Error Log#

Datadog Logs

text
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)이 발생했습니다. 이는 uploadCoveruploadFilerequest.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:87supportModel.uploadCover() 호출
  • image-cpobject.ts:264-281uploadCover 메서드가 createCoverUploadUrl()로 S3 presigned URL을 생성한 후 uploadFile()을 호출
  • image-cpobject.ts:93-117uploadFile 메서드에서 request.put()으로 S3에 파일 업로드 시도
  • Failure point: image-cpobject.ts:113-114.on('error') 핸들러에서 ETIMEDOUT 에러 캐치

실행 흐름:

  1. ThumbnailService::run에서 Asset 모델(key: s4yhjgpf62ma)에 대해 thumbnail 생성 후 cover 생성 흐름 진입:
typescript
// thumbnail-service.ts:83-88
if (supportModel.needCover) {
    await supportModel.downloadOriginal();
    await supportModel.preprocess();
    await supportModel.generateCover();
    await supportModel.uploadCover();  // 여기서 에러 발생
}
  1. uploadCovercreateCoverUploadUrl()로 S3 presigned URL을 획득한 후 uploadFile()을 호출:
typescript
// 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'
    });
    // ...
};
  1. uploadFile 메서드에서 request.put()으로 S3에 파일을 업로드하는데, TLS 읽기 단계에서 타임아웃 발생. 에러 핸들러에서 path (Node.js 모듈)filePath (함수 파라미터) 대신 사용하는 버그가 있음:
typescript
// 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를 올바르게 사용하고 있어, uploadFilepath 사용은 명백한 타이핑 실수입니다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-any-thumbnail-agent status:error "ImageCPObject"
Time range: 2026-04-09T09:55:00Z to 2026-04-09T11:55:00Z
text
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 업로드 중 타임아웃

에러 로그 원문:

json
{
  "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\"}"
}
json
{
  "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: pathfilePath로 변경. .on('response') 핸들러의 에러 로그에서 Node.js path 모듈 대신 함수 파라미터 filePath를 사용해야 합니다.
  • Line 114: pathfilePath로 변경. .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 업로드 타임아웃 빈도 추적:
text
service:cupixworks-any-thumbnail-agent status:error "uploadFile" "ETIMEDOUT"
  • cover 업로드 실패 전반 모니터링:
text
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
  • S3 업로드 지연 시간 메트릭 추가 검토 (custom metric으로 uploadFile elapsed time 기록)

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial — 두 줄의 변수명 수정(pathfilePath)으로 로깅 버그 해결. 네트워크 타임아웃 자체는 일시적 현상으로 발생 빈도 1회.