ES /docs

ImageCPObject::uploadFile | response path: {

RCA: ImageCPObject::uploadFile | path module bug + cover upload 500 error

Error Log#

Datadog Logs

text
ImageCPObject::uploadFile | response 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]
}, code: 500, message: Internal Server Error

Impact#

  • Service: cupixworks-any-thumbnail-agent
  • Team: hassan-allam
  • 발생 횟수: 2
  • 최초 발생: 2026-04-08T11:28:46.474Z
  • 최근 발생: 2026-04-08T11:29:06.717Z

Root Cause Summary#

이 에러에는 두 가지 문제가 겹쳐 있다. 첫 번째이자 근본 원인은 Asset의 cover 이미지를 S3 presigned URL로 업로드(uploadFile)할 때 서버가 HTTP 500을 반환한 것이다. **두 번째 문제(코드 버그)**는 ImageCPObject 클래스의 uploadFile 메서드 내 에러 로깅 코드가 filePath 파라미터 대신 path(Node.js의 path 모듈 객체)를 출력하는 변수 참조 오류이다. 이 로깅 버그로 인해 실제 어떤 파일의 업로드가 실패했는지 에러 로그에서 확인할 수 없고, 디버깅이 불가능해진다. 로그에 따르면 hassan-allam 팀의 Asset 모델(key 기반 ID) 2건의 cover 업로드가 실패했으며, BaseService::handlingMessageErrors에서 "error":"undefined response"로 처리되어 SQS 메시지가 삭제되고 작업이 종료되었다.

Technical Analysis#

Code Path#

  • Entry point: thumbnail-service.ts:87supportModel.uploadCover() 호출
  • uploadCover 메서드: image-cpobject.ts:264-281createCoverUploadUrl()로 presigned URL을 받고, uploadFile()을 호출
  • createCoverUploadUrl: cpasset.ts:111-116cupixApi.asset.createCoverUploadUrl()로 S3 presigned URL을 생성
  • Failure point: image-cpobject.ts:108 — HTTP 응답 상태 코드가 200이 아닌 500
  • 로깅 버그: image-cpobject.ts:109,114filePath 대신 path 모듈 참조

uploadFile 메서드의 에러 로깅 버그 (핵심):

typescript
// image-cpobject.ts:93-117
private uploadFile = (url: string, filePath: string, headers: any): Promise<void> => new Promise((resolve, reject) => {
    const cupixAuth = this.cupixAuth;
    logger.debug('ImageCPObject::uploadFile | start path: %s, size: %d', filePath, CPUtils.getFileSize(filePath));
    const sendReq = request.put(url, {
        headers: headers,
        body: fs.readFileSync(filePath)
    });

    sendReq
        .on('complete', async () => {
            await CPUtils.sleep(100);
            logger.debug('ImageCPObject::uploadFile | complete path: %s', filePath);
            resolve();
        })
        .on('response', res => {
            if (res.statusCode !== 200) {
                logger.error('ImageCPObject::uploadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
                //                                                                                     ^^^^ BUG: path 모듈 객체
                reject(cupixAuth.handleError(res));
            }
        })
        .on('error', err => {
            logger.error('ImageCPObject::uploadFile | path: %s, error: %s', path, JSON.stringify(err));
            //                                                                ^^^^ BUG: 동일한 path 모듈 참조 오류
            reject(cupixAuth.handleError(err));
        });
});

비교: downloadFile 메서드(같은 파일, line 76)는 filePath를 올바르게 사용한다:

typescript
// image-cpobject.ts:76
logger.error('ImageCPObject::downloadFile | response path: %s, code: %d, message: %s', filePath, res.statusCode, res.statusMessage);

실행 흐름:

  1. SQS에서 Asset 메시지 수신 (model.type: Asset, model.key: 문자열 키)
  2. CPAsset::setModel에서 cover_state !== None인 경우 _needCover = true 설정 (cpasset.ts:49-53)
  3. thumbnail-service.ts:83-88에서 cover 생성 후 업로드 실행
  4. createCoverUploadUrl → API 호출로 S3 presigned URL 획득 (cpasset.ts:113)
  5. uploadFile → presigned URL에 PUT 요청 → HTTP 500 응답
  6. reject(cupixAuth.handleError(res))BaseService::handlingMessageErrors 도달

기대 동작: cover 이미지가 S3 presigned URL에 성공적으로 업로드되고, 에러 발생 시 실제 파일 경로가 로그에 출력되어야 한다.

실제 동작: 업로드가 HTTP 500으로 실패했고, 에러 로그에 파일 경로 대신 Node.js path 모듈 객체가 출력되어 디버깅이 불가능하다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
Time: 2026-04-08T10:28:00Z ~ 2026-04-08T12:00:00Z
text
service:cupixworks-any-thumbnail-agent status:error
Time: 2026-04-08T10:28:00Z ~ 2026-04-08T12:00:00Z
text
service:cupixworks-any-thumbnail-agent status:info
Time: 2026-04-08T11:28:00Z ~ 2026-04-08T11:30:00Z

에러 로그 (2건):

첫 번째 에러 (20:28:46 KST):

json
{
  "timestamp": "2026-04-08 20:28:46 KST",
  "status": "error",
  "message": "ImageCPObject::uploadFile | response path: {\n  default: [Object],\n  resolve: [Getter],\n  ...\n}, code: 500, message: Internal Server Error"
}

두 번째 에러 (20:29:06 KST):

json
{
  "timestamp": "2026-04-08 20:29:06 KST",
  "status": "error",
  "message": "ImageCPObject::uploadFile | response path: {\n  default: [Object],\n  resolve: [Getter],\n  ...\n}, code: 500, message: Internal Server Error"
}

연관 에러 — BaseService 에러 핸들러 (2건):

json
{
  "timestamp": "2026-04-08 20:28:46 KST",
  "status": "error",
  "message": "BaseService::handlingMessageErrors | Error and message object - {\"error\":\"undefined response\",\"sqsMessage\":{\"MessageId\":\"d3550952-ab36-4137-9336-abe85bf10d45\",\"Attributes\":{\"ApproximateReceiveCount\":\"1\"}}}"
}
json
{
  "timestamp": "2026-04-08 20:29:06 KST",
  "status": "error",
  "message": "BaseService::handlingMessageErrors | Error and message object - {\"error\":\"undefined response\",\"sqsMessage\":{\"MessageId\":\"f515c21b-6538-4c2f-a972-85b369c9f26a\",\"Attributes\":{\"ApproximateReceiveCount\":\"1\"}}}"
}

실행 흐름 타임라인 (첫 번째 에러):

시간 (KST) 이벤트
20:28:37 BaseService::runByMessage | id: NaN — Asset 메시지 수신 (key 기반이므로 id는 NaN)
20:28:37 ThumbnailService::newModel | model id: NaN, type: Asset
20:28:37 ImageCPObject::downloadOriginal | url: .../assets/etq5ljia9q7h/download
20:28:38 ImageCPObject::generateThumbnail | begin
20:28:40 ImageCPObject::generateThumbnail | end — 1708ms
20:28:40 ImageCPObject::generateCover | begin
20:28:44 ImageCPObject::generateCover | end — 3794ms
20:28:46 ImageCPObject::uploadFile | response path: {path module}, code: 500
20:28:46 BaseService::handlingMessageErrors | "error":"undefined response"

cover 이미지 생성은 성공했으나 (3794ms, 1.9MB), S3 presigned URL로의 업로드에서 500 응답을 받았다. 같은 시간대의 다른 Asset 처리(dpwstjot4j82, pq1dpthm4ukv, q50ez2lw9u0s)는 성공적으로 처리되었으므로, 특정 Asset의 presigned URL 생성 또는 S3 상태에 일시적 문제가 있었을 가능성이 있다.

Fix Recommendation#

즉시 조치 (Critical)#

  1. 로깅 버그 수정image-cpobject.ts:109,114
    • pathfilePath로 변경 (2군데)
    • line 109: logger.error('ImageCPObject::uploadFile | response path: %s, code: %d, message: %s', path, ...) 에서 pathfilePath
    • line 114: logger.error('ImageCPObject::uploadFile | path: %s, error: %s', path, ...) 에서 pathfilePath
    • 이것은 1줄짜리 변경 2건으로 즉시 배포 가능

단기 개선 (1주 이내)#

  1. uploadFile 에러 로깅 강화 — 현재 500 에러 시 응답 body를 로깅하지 않아 S3 측 에러 원인을 파악할 수 없다. res.body 또는 응답 데이터를 함께 기록하도록 개선
  2. uploadFile 재시도 로직 — S3 presigned URL 업로드는 일시적 네트워크 이슈나 서버 과부하로 간헐적 실패할 수 있다. 1-2회 재시도 로직 추가 검토

장기 개선 (재발 방지)#

  1. TypeScript strict 설정 강화path 모듈과 filePath 문자열의 타입이 다르므로(PlatformPath vs string), logger.error의 format string 인자에 대한 타입 검사가 활성화되면 컴파일 타임에 잡을 수 있다. ESLint의 @typescript-eslint/no-unnecessary-type-assertion 또는 custom lint rule 검토
  2. 변수명 규칙 — 모듈 import를 * as path로 하면 지역 변수 path/filePath와 혼동 가능. * as nodePath 등 명확한 이름으로 import하는 컨벤션 도입 검토

Monitoring#

  • Cover 업로드 실패 모니터링:
text
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
  • 전체 에러 트렌드:
text
service:cupixworks-any-thumbnail-agent status:error
  • BaseService::handlingMessageErrors 에러 빈도 추적:
text
service:cupixworks-any-thumbnail-agent "handlingMessageErrors"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (로깅 버그 수정은 2줄 변경)
  • 500 에러 자체는 간헐적이며 (2건, 같은 시간대의 다수 Asset 처리는 성공), 로깅 버그 수정 후 재발 시 원인 파악이 가능해진다