ImageCPObject::uploadFile | response path: {
RCA: ImageCPObject::uploadFile | path module bug + cover upload 500 error
Error Log#
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:87—supportModel.uploadCover()호출 uploadCover메서드:image-cpobject.ts:264-281—createCoverUploadUrl()로 presigned URL을 받고,uploadFile()을 호출createCoverUploadUrl:cpasset.ts:111-116—cupixApi.asset.createCoverUploadUrl()로 S3 presigned URL을 생성- Failure point:
image-cpobject.ts:108— HTTP 응답 상태 코드가 200이 아닌 500 - 로깅 버그:
image-cpobject.ts:109,114—filePath대신path모듈 참조
uploadFile 메서드의 에러 로깅 버그 (핵심):
// 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를 올바르게 사용한다:
// image-cpobject.ts:76
logger.error('ImageCPObject::downloadFile | response path: %s, code: %d, message: %s', filePath, res.statusCode, res.statusMessage);
실행 흐름:
- SQS에서 Asset 메시지 수신 (
model.type: Asset,model.key: 문자열 키) CPAsset::setModel에서cover_state !== None인 경우_needCover = true설정 (cpasset.ts:49-53)thumbnail-service.ts:83-88에서 cover 생성 후 업로드 실행createCoverUploadUrl→ API 호출로 S3 presigned URL 획득 (cpasset.ts:113)uploadFile→ presigned URL에 PUT 요청 → HTTP 500 응답reject(cupixAuth.handleError(res))→BaseService::handlingMessageErrors도달
기대 동작: cover 이미지가 S3 presigned URL에 성공적으로 업로드되고, 에러 발생 시 실제 파일 경로가 로그에 출력되어야 한다.
실제 동작: 업로드가 HTTP 500으로 실패했고, 에러 로그에 파일 경로 대신 Node.js path 모듈 객체가 출력되어 디버깅이 불가능하다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
Time: 2026-04-08T10:28:00Z ~ 2026-04-08T12:00:00Z
service:cupixworks-any-thumbnail-agent status:error
Time: 2026-04-08T10:28:00Z ~ 2026-04-08T12:00:00Z
service:cupixworks-any-thumbnail-agent status:info
Time: 2026-04-08T11:28:00Z ~ 2026-04-08T11:30:00Z
에러 로그 (2건):
첫 번째 에러 (20:28:46 KST):
{
"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):
{
"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건):
{
"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\"}}}"
}
{
"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)#
- 로깅 버그 수정 —
image-cpobject.ts:109,114path→filePath로 변경 (2군데)- line 109:
logger.error('ImageCPObject::uploadFile | response path: %s, code: %d, message: %s', path, ...)에서path→filePath - line 114:
logger.error('ImageCPObject::uploadFile | path: %s, error: %s', path, ...)에서path→filePath - 이것은 1줄짜리 변경 2건으로 즉시 배포 가능
단기 개선 (1주 이내)#
- uploadFile 에러 로깅 강화 — 현재 500 에러 시 응답 body를 로깅하지 않아 S3 측 에러 원인을 파악할 수 없다.
res.body또는 응답 데이터를 함께 기록하도록 개선 - uploadFile 재시도 로직 — S3 presigned URL 업로드는 일시적 네트워크 이슈나 서버 과부하로 간헐적 실패할 수 있다. 1-2회 재시도 로직 추가 검토
장기 개선 (재발 방지)#
- TypeScript strict 설정 강화 —
path모듈과filePath문자열의 타입이 다르므로(PlatformPath vs string),logger.error의 format string 인자에 대한 타입 검사가 활성화되면 컴파일 타임에 잡을 수 있다. ESLint의@typescript-eslint/no-unnecessary-type-assertion또는 custom lint rule 검토 - 변수명 규칙 — 모듈 import를
* as path로 하면 지역 변수path/filePath와 혼동 가능.* as nodePath등 명확한 이름으로 import하는 컨벤션 도입 검토
Monitoring#
- Cover 업로드 실패 모니터링:
service:cupixworks-any-thumbnail-agent status:error "uploadFile"
- 전체 에러 트렌드:
service:cupixworks-any-thumbnail-agent status:error
BaseService::handlingMessageErrors에러 빈도 추적:
service:cupixworks-any-thumbnail-agent "handlingMessageErrors"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (로깅 버그 수정은 2줄 변경)
- 500 에러 자체는 간헐적이며 (2건, 같은 시간대의 다수 Asset 처리는 성공), 로깅 버그 수정 후 재발 시 원인 파악이 가능해진다