매핑 파일 업로드
R8/ProGuard mapping.txt를 올려 난독화된 스택 트레이스를 원래 클래스, 메서드, 줄 번호로 복원합니다.
R8로 축소한 릴리스 빌드의 스택 트레이스는 a.b.c(Unknown Source:12)처럼 남습니다. 그 빌드의 mapping.txt를 올려 두면 Sophonz가 R8 retrace로 원래 이름과 줄 번호를 되돌려 보여 줍니다. 매핑 파일은 앱의 앱 버전 아래에 보관되고, 빌드 ID로 정확히 매칭됩니다.
복원되는 대상
| 데이터 | 속성 | 내용 |
|---|---|---|
| 크래시, 처리된 예외 | exception.stacktrace | 예외가 난 스레드의 프레임과 Caused by 체인 |
| 크래시 시점의 다른 스레드 | spz.android.threads | 스레드 이름, 상태, 프레임 |
R8이 인라인한 메서드는 원래 호출 순서대로 여러 프레임으로 펼쳐집니다. java., android., androidx., kotlin. 등 플랫폼 패키지 프레임은 난독화되지 않으므로 그대로 표시됩니다.
mapping.txt 위치
isMinifyEnabled = true인 빌드 타입을 빌드하면 R8이 매핑 파일을 만듭니다.
app/build/outputs/mapping/<빌드 배리언트>/mapping.txtCAUTION — 매핑은 빌드마다 다릅니다
코드가 같아도 다시 빌드하면 난독화 이름이 달라질 수 있습니다. 스토어에 올린 바로 그 빌드의 mapping.txt를 올리세요.
방법 1: Gradle 플러그인 (권장)
Sophonz Gradle 플러그인을 적용했다면 R8 작업이 끝날 때 매핑 파일을 자동으로 올립니다. 플러그인 적용 방법은 설치를 참고하세요.
설정
gradle.properties에 업로드 주소를 지정합니다.
sophonz.baseUrl=https://app.sophonz.aisophonz-config.json에 자격 증명을 넣습니다. 둘 중 하나라도 비어 있으면 업로드 작업이 등록되지 않습니다.
{
"app_id": "sophz",
"api_token": "sophonz_pat_..."
}| 키 | 값 |
|---|---|
api_token | 앱의 액세스 토큰. 설정 > 프로젝트 > 앱 > 액세스 토큰에서 발급합니다 |
app_id | 플러그인이 요구하는 5자 문자열. Sophonz 서버는 이 값을 쓰지 않고 토큰으로 앱을 정하므로 임의의 5자면 됩니다 |
NOTE — 토큰을 커밋하지 마세요
CI에서는 sophonz-config.json에서 api_token을 빼고 환경 변수 SOPHONZ_API_TOKEN으로 넘기세요. app_id도 SOPHONZ_APP_ID로 지정할 수 있습니다.
- name: Build release
env:
SOPHONZ_API_TOKEN: ${{ secrets.SOPHONZ_TOKEN }}
SOPHONZ_APP_ID: sophz
run: ./gradlew assembleRelease동작
- 플러그인은 빌드마다 빌드 ID를 만들어 앱에 넣고, 같은 값으로 매핑 파일을 올립니다. SDK는 이 값을
app.build_id로 보내므로 스택 트레이스와 매핑이 빌드 단위로 정확히 연결됩니다. - 업로드 요청에는 버전이 없습니다. 이 빌드가 텔레메트리를 보내면 그 빌드 ID가 속한 앱 버전 아래로 자동으로 옮겨집니다. 그 전까지는 설정의 버전을 아직 모르는 빌드에 표시됩니다.
- 매핑 파일은 zstd로 압축돼 전송됩니다.
| Gradle 속성 | 기본값 | 설명 |
|---|---|---|
sophonz.baseUrl | — | 업로드 주소. 비어 있으면 업로드 단계에서 빌드가 실패합니다 |
sophonz.disableMappingFileUpload | false | true면 매핑 업로드를 등록하지 않습니다 |
sophonz.failBuildOnUploadErrors | — | 업로드 실패 시 빌드를 실패시킬지 여부 |
방법 2: @sophonz/cli
Gradle 플러그인을 쓰지 않거나 업로드를 별도 단계로 두려면 CLI를 사용합니다. Node.js 20 이상이 필요합니다.
export SOPHONZ_TOKEN=sophonz_pat_...
npx @sophonz/cli upload-mapping \
--file app/build/outputs/mapping/release/mapping.txt \
--version 1.4.0| 옵션 | 설명 |
|---|---|
-f, --file | mapping.txt (필수) |
-v, --version | 앱 버전. SDK가 보내는 service.version(versionName)과 같은 값 |
-b, --build-id | 빌드 ID. SDK가 보내는 app.build_id와 같은 값 |
--type | R8(기본) 또는 PROGUARD |
-t, --token | 액세스 토큰. 기본값은 SOPHONZ_TOKEN |
--version과 --build-id 중 하나는 있어야 합니다.
- name: Build release
run: ./gradlew assembleRelease
- name: Upload mapping
env:
SOPHONZ_TOKEN: ${{ secrets.SOPHONZ_TOKEN }}
run: |
# assembleRelease가 남기는 메타데이터에서 versionName을 읽습니다
VERSION=$(jq -r '.elements[0].versionName' app/build/outputs/apk/release/output-metadata.json)
npx @sophonz/cli upload-mapping \
--file app/build/outputs/mapping/release/mapping.txt \
--version "$VERSION"방법 3: curl
curl -X POST https://app.sophonz.ai/api/v1/mapping-files \
-H "Authorization: Bearer $SOPHONZ_TOKEN" \
-F type=R8 \
-F version=1.4.0 \
-F file=@app/build/outputs/mapping/release/mapping.txt매칭 방식
스택 트레이스를 열면 다음 순서로 매핑 파일을 찾습니다.
- 트레이스의
app.build_id와 빌드 ID가 같은 파일 - 없으면 트레이스의
service.version과 버전이 같은 파일 중 가장 최근에 올린 것
CAUTION — 버전만으로 올릴 때
같은 버전으로 여러 번 빌드해 배포했다면 버전 매칭은 마지막에 올린 매핑을 씁니다. 이전 빌드의 트레이스는 잘못된 이름으로 복원될 수 있습니다. 버전을 재사용한다면 빌드 ID와 함께 올리세요. Gradle 플러그인은 항상 빌드 ID로 올립니다.
확인
설정 > 프로젝트 > 앱 > 매핑 파일에 앱의 버전 목록이 있습니다. 목록은 텔레메트리에서 자동으로 채워지고, 버전마다 올라온 매핑 파일과 빌드 ID가 보입니다. 텔레메트리를 보냈는데 매핑이 없는 버전에는 경고가 표시됩니다. 출시 전 버전은 버전 추가로 먼저 만들어 둘 수 있습니다.
앱이 WebView를 쓴다면 같은 화면에 웹 버전 목록도 함께 표시됩니다. WebView의 JavaScript 에러는 소스맵으로 복원됩니다.
크래시나 예외가 들어오면 로그나 트레이스에서 행을 펼칩니다. 스택 트레이스 뷰어에서 예외가 난 스레드가 맨 위에 오고, Caused by와 크래시 시점의 다른 스레드가 스레드 목록에 나옵니다. 앱 패키지의 프레임은 강조되고 연속된 플랫폼 프레임은 접혀서 보입니다. 복원 / 원본 전환으로 기기가 보낸 그대로의 프레임도 볼 수 있습니다.
업로드 API
POST https://app.sophonz.ai/api/v1/mapping-files
Authorization: Bearer sophonz_pat_...
Content-Type: multipart/form-data| 필드 | 설명 |
|---|---|
type | R8 또는 PROGUARD |
file | mapping.txt. zstd·gzip 압축 파일도 받습니다 |
version | 앱 버전 |
buildId | 빌드 ID |
version과 buildId 중 하나는 필수입니다. Gradle 플러그인은 같은 저장소를 POST /v2/store/proguard로 호출합니다.
| 상태 | error | 원인 |
|---|---|---|
401 | unauthorized | 토큰이 없거나 잘못됐거나 만료·폐기됨 |
400 | fileMissing / typeInvalid / versionRequired | 필드 누락 |
413 | fileTooLarge | 500MB 초과 |
422 | mappingInvalid | 매핑 파일로 읽을 수 없음 |
500 | storageFailed | 서버가 파일을 저장하지 못함 |