ES /docs

JSON::ParserError: Empty input (after ) at line 1, column 1 [parse.c:1060] in 'pano_mask_enhanced

RCA: JSON::ParserError Empty input in 'pano_mask_enhanced'

Overview#

What Happened#

cupixworks-api (tesla) 의 PUT /api/v1/captures/{id}/meta/pano_mask_enhanced 엔드포인트(Api::V1::CapturesController#update_meta_by_key)에서 요청 본문(request body)이 비어 있을 때 JSON.parse("")JSON::ParserError: Empty input 를 발생시켰다. 이 예외는 해당 액션의 begin/rescueCupix::Errors::Parameter 만 포착하도록 되어 있어 unhandled 상태로 전파, HTTP 500 으로 매핑됐다. 2026-05-27 ~ 2026-07-14 사이 총 10건의 저빈도 산발 발생이다.

Quick Facts#

Field Value
exception.class JSON::ParserError
exception.message Empty input (after ) at line 1, column 1 [parse.c:1060]
top_frame app/controllers/concerns/metable_controller.rb:48
runtime Ruby (json gem 2.10.1) / Rails
env production/dev (cupixworks-api)

Affected Teams#

Team / Domain Error Count Impact
cupixworks-api (meta/pano_mask_enhanced 엔드포인트 호출 클라이언트) 10 (전체 기간) 빈/malformed body 요청이 400 대신 500 으로 응답됨. 실제 데이터 손상 없음

Timeline#

  1. 2026-05-27 18:01 KST — 최초 발생 (first_seen)
  2. 2026-07-14 20:46 KST — 최근 발생 (last_seen)
  3. 2026-08-06 (현재) — RCA 수행. last_seen 이 14일 retention 창 밖이라 현재 로그 0건

Error Log#

Datadog Logs

text
Empty input (after ) at line 1, column 1 [parse.c:1060] in 'pano_mask_enhanced'

Impact#

  • Service: cupixworks-api (tesla)
  • 발생 횟수: 10
  • 최초 발생: 2026-05-27 18:01 KST
  • 최근 발생: 2026-07-14 20:46 KST

Root Cause Summary#

update_meta_by_key 액션은 JSON.parse(request.raw_post) 로 요청 본문을 파싱한다. 클라이언트가 PUT .../meta/pano_mask_enhanced빈 본문으로 보내면 request.raw_post 가 빈 문자열이 되고, json gem 2.10.1 이 JSON::ParserError: Empty input (after ) at line 1, column 1 [parse.c:1060] 를 발생시킨다. 그러나 이 액션의 begin/rescue 블록은 rescue Cupix::Errors::Parameter 만 포착하도록 되어 있어 JSON::ParserError 는 잡히지 않는다. JSON::ParserError < StandardErrorclient_error_controller.rb / server_error_controller.rb 의 어떤 rescue_from 목록에도 없고(특히 RuntimeError 의 하위가 아니라 502 rescue 에도 미포함), catch-all rescue_from StandardError 도 없어 Rails 기본 처리로 HTTP 500 이 된다. 즉 클라이언트 입력 오류(빈/malformed body, 마땅히 400)가 서버 오류 500 으로 mis-map 된다. 형제 액션 update_meta 는 동일 파싱을 rescue JSON::ParserError → ARG10004 (400) 로 올바르게 처리하고 있어, 이는 두 액션 간 error handling 불일치가 root cause다.

Technical Analysis#

Code Path#

  • Entry point: PUT /api/v1/captures/*/meta/*meta_keyApi::V1::CapturesController#update_meta_by_key (route config/routes.rb:320)
  • Failure point: app/controllers/concerns/metable_controller.rb:48

경로: 클라이언트가 meta_key = pano_mask_enhanced 로 빈 본문 PUT → request.raw_post == ""JSON.parse("") raise.

app/controllers/concerns/metable_controller.rb:47-63ruby
    begin
      parsed_meta = JSON.parse(request.raw_post)   # 빈 본문이면 JSON::ParserError 발생
      @model.meta[params[:meta_key]] = parsed_meta
      @model.skip_entrypoint_flush = true if @model.respond_to?(:skip_entrypoint_flush)
      @model.save
      if CLUSTER_META_LOGGING_ENABLED && @model.instance_of?(::Cluster)
        meta_str = parsed_meta.to_json rescue ''
        if meta_str.bytesize < 10_000
          Cupix::Logger.info("Meta updated by key: #{params[:meta_key]}. Cluster #{@model.id}",
                             class: @model.class.name,
                             function: __method__,
                             module: 'MetableController',
                             meta: parsed_meta)
        end
      end
    rescue Cupix::Errors::Parameter => e     # JSON::ParserError 는 여기서 안 잡힘
      raise Cupix::Errors::Parameter.new(code: 'ARG10004', reason: e.to_s, message: e.message)

params[:meta_key] 는 라우트 wildcard *meta_keypano_mask_enhanced 값을 가진다. ET 대표 메시지의 in 'pano_mask_enhanced' 접미사는 이 meta_key 경로 세그먼트를 반영한 것으로, json gem 2.10.1 예외 메시지 자체(Empty input (after ) at line 1, column 1 [parse.c:1060])에는 in '...' 이 포함되지 않는다(Datadog Error Tracking 이 그룹핑 컨텍스트로 부착).

형제 액션 update_meta 는 동일한 파싱을 올바르게 방어한다 — 대조:

app/controllers/concerns/metable_controller.rb:19-24ruby
    begin
      @model.meta = JSON.parse(request.raw_post)
      @model.skip_entrypoint_flush = true if @model.respond_to?(:skip_entrypoint_flush)
      @model.save
    rescue JSON::ParserError                       # 올바르게 포착 → 400
      raise Cupix::Errors::Parameter.new(code: 'ARG10004', reason: 'Failed to parse meta value')

기대 동작: 빈/malformed body 는 클라이언트 입력 오류이므로 update_meta 처럼 ARG10004 (HTTP 400) 로 매핑돼야 한다. 실제 동작: update_meta_by_keyJSON::ParserError 를 놓쳐 unhandled → HTTP 500.

Cupix::Errors::Parameter 는 400 으로 매핑되지만, 그 앞단의 JSON.parse 실패는 이 클래스가 아니라 raw JSON::ParserError 이므로 rescue 목록 어디에도 걸리지 않는다:

app/controllers/concerns/client_error_controller.rb:7-14ruby
    rescue_from Cupix::Errors::Unknown,
                Cupix::Errors::Resource,
                Cupix::Errors::Session,
                Cupix::Errors::Parameter,   # JSON::ParserError 는 이 계열이 아님
                Cupix::Errors::Entity,
                Cupix::Errors::Billing,
                Cupix::Errors::InvalidState,
                Cupix::Errors::Siteinsights, with: :client_400_error

server_error_controller.rb:18-20 의 502 rescue 는 RuntimeError 만 포함하는데 JSON::ParserErrorStandardError 직속 하위이지 RuntimeError 하위가 아니라 이 역시 미포착. catch-all rescue_from StandardError 도 없어 Rails 기본 :internal_server_error → 500 이 된다.

Log Evidence#

Datadog 쿼리 (성공 요청 — 엔드포인트/경로 확인):

text
service:cupixworks-api "pano_mask_enhanced"

정상 요청 로그(엔드포인트/액션/meta_key 경로 세그먼트 확증):

json
{
  "timestamp": "2026-08-06 16:43:07",
  "status": "info",
  "message": "[200] PUT /api/v1/captures/86578/meta/pano_mask_enhanced (Api::V1::CapturesController#update_meta_by_key)"
}

에러 변형 검색(현재 창 0건 — retention 밖):

text
service:cupixworks-api status:error "Empty input" "pano_mask_enhanced"

결과: Found 0 logs (last_seen 2026-07-14 은 now-14d 밖). Error Tracking 이 retention 초과 occurrence 를 집계해 이 이슈를 유지.

참고 — 동일 Empty input ... [parse.c:1060] 메시지 포맷이 별개 코드경로(BimAi get failed)에서도 나타나며, 그쪽은 파싱 대상 응답 본문(AWS Elastic Beanstalk Node.js sample HTML)을 in '...' 로 붙인다. 이는 별개 ET 이슈이므로 cross-merge 금지:

text
service:cupixworks-api status:error "Empty input"
text
BimAi get failed: Empty input (after ) at line 1, column 1 [parse.c:1060] in '<!DOCTYPE html> ...'

json gem 버전 확인:

text
Gemfile.lock: json (2.10.1)

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 update_meta_by_keyJSON.parse(request.raw_post) 가 빈 본문에서 JSON::ParserError 를 발생시키나 rescue Cupix::Errors::Parameter 만 있어 미포착 → unhandled 500 (client 입력 오류의 status mis-mapping) metable_controller.rb:48 JSON.parse(request.raw_post); :62 rescue Cupix::Errors::Parameter 만; client/server error controller rescue 목록에 JSON::ParserError 없음; update_meta:23rescue JSON::ParserError 로 올바르게 처리(불일치); 메시지 Empty input = 빈 문자열 파싱 시그니처; 성공 요청 로그로 엔드포인트/액션 확증 Confirmed
H2 대표 메시지가 stale 이고 실제 현재 발생은 pano_mask_enhanced 가 아닌 다른 meta_key/코드경로 같은 [parse.c:1060] 포맷이 BimAi get failed 경로에도 존재 service:cupixworks-api status:error "Empty input" "pano_mask_enhanced" = 0건이나 이는 retention(14d) 밖이라 부재를 의미하지 않음; pano_mask_enhanced 는 tesla 고유 meta_key 이고 BimAi 경로는 별개 ET 이슈(다른 body 부착) → 우리 이슈의 대표는 endpoint 세그먼트로 신뢰 가능, stale 아님 Rejected
H3 meta[:pano_mask_enhanced] 값의 역직렬화(serialize)에서 JSON 파싱 실패 Capturepano_mask_enhanced 를 meta 키로 노출 meta serialize 는 Cupix::Util::FlexibleHashYAML 사용(flexible_hash.rb:6 YAML.safe_load), JSON 아님; pano_mask_enhanced 참조는 tesla 전체 2파일뿐이며 JSON.parse 없음 Rejected
H4 외부 의존성 장애로 인한 downstream 5xx status-board svc:cupixworks-api::unknown active 인시던트는 2026-08-06 05:42~07:21 UTC 배포 버스트로 이 클러스터 last_seen(2026-07-14)과 무관; 예외가 로컬 JSON.parse 라 downstream 아님 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • app/controllers/concerns/metable_controller.rb:47-63 update_meta_by_keybegin/rescuerescue JSON::ParserError 를 추가해 형제 액션 update_meta (:23-24) 와 동일하게 Cupix::Errors::Parameter (ARG10004) → HTTP 400 으로 매핑한다. 이렇게 하면 빈/malformed body 라는 클라이언트 입력 오류가 500 이 아닌 400 으로 정확히 반환된다. (기존 rescue Cupix::Errors::Parameter 절은 유지)

단기 개선 (1주 이내)#

  • 두 액션(update_meta, update_meta_by_key)의 파싱/에러 처리를 공통 헬퍼로 통일해 향후 재발/불일치를 방지한다. parse_meta_value (:73-106) 가 이미 유사한 유연 파싱을 하고 있으므로 raw_post 파싱도 단일 지점으로 수렴시키는 방향을 검토한다.

장기 개선 (재발 방지)#

  • 컨트롤러 진입 단계의 body/파라미터 계약 검증(schema validation)을 도입해 JSON.parse 전에 빈 본문/형식 오류를 4xx 로 일관되게 처리한다. 광범위 rescue_from StandardError 는 실제 nil-ref 등 진짜 버그를 가리므로 지양하고, 파싱 실패만 좁게 4xx 로 매핑한다.

Monitoring#

수정 후 이 엔드포인트에서 500 이 사라지고 400(ARG10004)로 전환되는지 확인.

파싱 실패(수정 후 400)로 재분류되는 요청 추이:

text
service:cupixworks-api "ARG10004" "meta"

meta/pano_mask_enhanced 엔드포인트의 5xx 응답(수정 후 0 이어야 함):

text
service:cupixworks-api "meta/pano_mask_enhanced" ("[500]" OR status:error)

Empty input 파싱 예외 재발 감지:

text
service:cupixworks-api status:error "Empty input" "pano_mask_enhanced"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (한 액션에 rescue JSON::ParserError 절 1개 추가, 형제 액션에 이미 존재하는 패턴 차용)

Noise Verdict#

noise — 빈/malformed 요청 본문이라는 클라이언트 입력 오류가 status-code mis-mapping 으로 500 이 됐을 뿐 데이터 손상이나 서버 로직 결함이 없고, 조치는 4xx 재매핑(품질 개선)이다.