ES /docs

Invalid location format: lon

RCA: Invalid location format: lon

Overview#

What Happened#

2026-07-14 20:46 KST 프로덕션 cupixworks-api 에서 Facility#set_parameters 처리 중 params[:location] 이 JSON 객체가 아닌 문자열 "lon" 으로 들어왔다. JSON.parse("lon")JSON::ParserError 를 발생시켰고 코드가 이를 error 레벨로 로깅한 뒤 그대로 재발생시켰다. 발생 건수는 1건이며 tenant cupix, region us-west-2 에서 관측됐다.

Quick Facts#

Field Value
exception.class JSON::ParserError
exception.message Invalid location format: lon
top_frame app/concerns/parameter.rb:56-59
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
cupixworks-api / Facility update 1 Facility update 요청 1건 실패, 클라이언트 500 응답 (unhandled JSON::ParserError 는 controller 에 rescue_from 이 없음)

Timeline#

  1. 2026-07-14 20:46:05 KSTFacility#set_parameters 진입, params[:location] = "lon" (String)
  2. 2026-07-14 20:46:05 KSTJSON.parse("lon")JSON::ParserError 발생
  3. 2026-07-14 20:46:05 KSTCupix::Logger.error("Invalid location format: lon", class: "Facility", function: "set_parameters", module: "Parameter") 기록 후 raise e
  4. 2026-07-14 20:46:05 KST — error-sweeper collector 가 로그를 수집, cluster 145ece24-ddad-4fbe-9542-99f426fb5ffb 생성

Error Log#

Datadog Logs

text
Invalid location format: lon

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-07-14 20:46 KST
  • 최근 발생: 2026-07-14 20:46 KST

블래스트 반경은 좁다. 최근 24시간 검색에서 동일 메시지가 1건만 존재하고, 다른 tenant/region 확산 흔적이 없다. 클라이언트가 잘못된 location 페이로드("lon" 문자열)를 한 번 보낸 결과로 판단된다.

Root Cause Summary#

클라이언트가 Facility update 요청에 location 파라미터로 JSON 객체 문자열(예: {"lat":37.5,"lon":127.0}) 대신 리터럴 문자열 "lon" 을 전달했다. Parameter#set_parametersparams[:location] 이 String 인 경우 JSON.parse 로 파싱을 시도하는데, "lon" 은 유효한 JSON 이 아니므로 JSON::ParserError 가 발생한다. 코드는 이 예외를 error 레벨로 로깅한 뒤 그대로 재발생시키고, Api::V1::FacilitiesController 를 포함한 어떤 controller 에도 rescue_from JSON::ParserError 가 정의돼 있지 않아 미포착 예외로 처리돼 500 응답을 유발한다. 즉 원인은 클라이언트의 잘못된 입력이지만, 서버가 이를 400 Bad Request 가 아닌 500 server error 로 취급하고 error 레벨 로그를 남기는 것이 결함이다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/facilities_controller.rb:125-129 (FacilitiesController#updaterepository_instance.update(params))
  • 파라미터 처리 진입: app/concerns/parameter/facility.rb:14-18 (Parameter::Facility#set_parameterssuper 로 상위 Parameter#set_parameters 호출)
  • Failure point: app/concerns/parameter.rb:53-60params[:location] 이 String 인 경우 JSON.parse 호출

Controller 진입:

app/controllers/api/v1/facilities_controller.rb:125-129ruby
def update
  @model = repository_instance.update(params)

  super
end

파싱 실패 지점:

app/concerns/parameter.rb:53-72ruby
if params[:location].present? && @model.respond_to?(:location)
  if params[:location].is_a?(String)
    begin
      location = JSON.parse(params[:location])
    rescue => e
      Cupix::Logger.error("Invalid location format: #{params[:location]}", class: @model.class.name, function: __method__, module: 'Parameter')
      raise e
    end
  else
    location = params[:location]
  end

  if location['lat'].present? || location[:lat].present?
    @model.latitude = location['lat'] || location[:lat]
  end

  if location['lon'].present? || location[:lon].present?
    @model.longitude = location['lon'] || location[:lon]
  end
end

기대 동작: params[:location]{"lat": <float>, "lon": <float>} 형태의 JSON 문자열이거나 Hash. JSON.parse 결과에서 lat/lon 을 추출해 모델에 설정.

실제 동작: params[:location] == "lon" (문자열 리터럴). JSON.parse("lon") 이 유효한 JSON 이 아니므로 JSON::ParserError 발생. 코드는 error 레벨로 로깅하고 그대로 re-raise.

Controller 계층에서의 재처리 부재:

app/controllers/concerns/client_error_controller.rb:16-17ruby
rescue_from ActionController::ParameterMissing,
            ActiveRecord::RecordInvalid, with: :invalid_parameter_400_error

JSON::ParserErrorClientErrorControllerrescue_from 목록에 없고, ServerErrorController 에도 없다. Ruby 기본 예외 계층상 JSON::ParserErrorStandardError 를 상속하지만 위의 어떤 매칭에도 걸리지 않으므로 Rails 의 기본 500 응답 경로를 탄다. 이는 참고로 ActionController::ParameterMissing 이 400 으로 매핑되는 것과 대조된다.

동일 파일 내 다른 필드는 JSON::ParserError 를 별도로 처리하지만 location 은 그렇지 않다:

app/concerns/parameter/annotation.rb:140 · app/concerns/parameter/bim.rb:98 · app/concerns/parameter/review.rb:25ruby
rescue JSON::ParserError => e

즉 프로젝트 전반의 컨벤션은 JSON::ParserError 를 잡아 Cupix::Errors::Parameter (→ 400) 로 재발생하는 것이나, parameter.rblocation 블록은 rescue 를 rescue => e 로 광범위하게 잡아 error 로그 후 원본 예외를 그대로 던진다.

Log Evidence#

사용한 Datadog 쿼리 (클러스터 파일 링크 그대로):

text
service:cupixworks-api status:error @environment:production "Invalid location format: lon"

시간 창: 2026-07-14 20:46 ~ 22:47 KST (URL 기준 from_ts=1784025960000 to_ts=1784033220000).

핵심 로그 항목:

json
{
  "timestamp": "2026-07-14 20:46:05",
  "status": "error",
  "message": "Invalid location format: lon",
  "class": "Facility",
  "function": "set_parameters"
}

추가로 넓힌 검색 (service:cupixworks-api "Invalid location format", now-24h) 결과도 위 1건만 반환됐다. 즉 이번 스파이크가 아니라 단발성 이벤트다.

Status board 조회 결과 이 클러스터는 svc:cupixworks-api::unknown 스코프이며 현재 active 인시던트는 없다 (recent 7일 목록에 여러 resolved 인시던트가 있으나 본 클러스터와의 상관관계 없음).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 클라이언트가 location 파라미터에 유효하지 않은 JSON 문자열("lon")을 전달했고, Parameter#set_parametersJSON.parse 가 실패해 error 로그 후 재발생 로그 메시지가 Invalid location format: lon 이며 parameter.rb:58Cupix::Logger.error("Invalid location format: #{params[:location]}", ...) 템플릿과 정확히 일치. 로그의 class: Facility, function: set_parameters 태그가 parameter.rb:58class: @model.class.name, function: __method__ 와 부합. params[:location] 이 정상 형태였다면 is_a?(String) 분기에서 JSON.parse 가 성공했을 것 ({"lon":...}) 또는 is_a?(String) 이 false 였을 것 Confirmed
H2 배포/코드 회귀로 인해 정상 payload 도 파싱에 실패하기 시작함 없음 24시간 내 동일 메시지 1건. 다른 tenant/region 확산 없음. 최근 parameter.rb 커밋은 TSLA-9920 convert latitude and longitude to Float (2025-06-19) 로 location 파싱 로직 변경 없음 Rejected
H3 특정 다운스트림 서비스(예: 지오코딩 API) 장애로 인해 location 이 잘못 채워짐 없음 오류는 순수하게 요청 파라미터 파싱 단계에서 발생하며 외부 호출 없음. parameter.rb:56 는 로컬 JSON.parse Rejected
H4 기존 s3/es dependency 인시던트의 파생 Status board 에 최근 svc:cupixworks-api::unknown 인시던트 다수 존재 본 클러스터 발생 시각(2026-07-14 20:46 KST) 에 active dep:* 인시던트 없음. recent svc:* 인시던트는 모두 다른 클러스터 세트 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • app/concerns/parameter.rb:53-63location 파싱 블록에서 rescue 를 JSON::ParserError 로 좁히고, 재발생 예외를 클라이언트 오류로 매핑되는 Cupix::Errors::Parameter 로 변환한다. 프로젝트의 다른 모듈이 이미 이 컨벤션을 따른다 — app/concerns/parameter/annotation.rb:140, app/concerns/parameter/bim.rb:98, app/concerns/parameter/review.rb:25, app/concerns.parameter.rb:180-181parse_to_json 헬퍼가 동일한 패턴을 보여준다.
  • 로그 레벨을 errorwarn 으로 낮춘다. 잘못된 클라이언트 입력은 서버 결함이 아니며 error 알림/on-call 노이즈를 유발한다. 메모리의 원칙 "Assess error severity during RCA" 및 "Scope warn-level downgrades to the specific exception class" 를 준수해, rescue JSON::ParserError 만 warn 으로 낮추고 상위 예외는 유지한다.
  • 파일 위치: app/concerns/parameter.rb:53-63

단기 개선 (1주 이내)#

  • parameter.rbparse_to_json (app/concerns/parameter.rb:165-182) 헬퍼를 location 블록에도 재사용해 파싱 로직을 통일한다. 현재 parse_to_json 은 파싱 실패 시 Cupix::Errors::Parameter 를 발생시켜 400 으로 매핑되도록 이미 설계되어 있다.
  • Controller 계층에서 JSON::ParserError 에 대한 안전망을 추가 (ClientErrorController#invalid_parameter_400_error 목록에 포함) — 개별 concern 이 rescue 를 누락하더라도 400 이 보장되도록.

장기 개선 (재발 방지)#

  • Facility update 파라미터 스키마 검증을 Strong Parameter + JSON schema/dry-schema 등으로 상단 계층에서 수행. location 필드는 {lat: Float, lon: Float} 로 정형화해 문자열 파싱을 근본적으로 제거한다.
  • API 명세(cupix-api specs)에 location 필드 타입을 명시하고 SDK/문서로 클라이언트 오사용을 예방한다.

Monitoring#

Datadog dashboard timeseries widget 에 붙일 수 있는 쿼리 (writing-datadog-monitoring-queries 규칙 준수 — pipe/stats/count by(...) 미사용):

text
service:cupixworks-api status:error "Invalid location format"
text
service:cupixworks-api @class:Facility @function:set_parameters status:error
  • 위 쿼리로 24시간/7일 발생 추이를 모니터링한다. 반복적으로 튀면(예: 시간당 5건 이상) 특정 클라이언트/버전이 잘못된 payload 를 보내고 있을 가능성을 조사한다.
  • 즉시 조치 후에는 위 로그가 status:warn 으로 이동하므로 error 카운트는 0 이어야 한다:
text
service:cupixworks-api status:error "Invalid location format"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial

단발성 클라이언트 입력 오류이며 서비스 전반 영향 없음. 코드 수정 범위는 parameter.rb 내 몇 줄에 국한되고, 프로젝트 내 다수 유사 사례(annotation.rb, bim.rb, review.rb) 가 이미 동일 컨벤션을 채택하고 있어 회귀 위험이 낮다.