GA4에 커스텀 측정기준을 등록해 뒀는데 보고서를 열면 값이 하나도 안 담기고 (not set)만 나오는 경우가 있습니다. 대개는 수집이 안 되는 게 아니라 파라미터 타입 문제입니다. 측정기준은 문자열만 읽기 때문에, putLong으로 보낸 숫자는 등록해 둬도 값이 통째로 비어 있습니다. 제 앱 네 개가 전부 그 상태였고 30일치 1,209건이 남김없이 (not set)이었는데, 이벤트 수는 정확하게 나오는 탓에 한참 동안 눈치채지 못했습니다.
어쩌다 그렇게 됐는지부터 적겠습니다. 앱에 계측을 심었습니다. 목록 화면이 열릴 때 몇 건이 실렸는지 같이 보내도록 했습니다.
val params = Bundle().apply {
putString("source", "cache")
putLong("item_count", list.size.toLong()) // 몇 건 실렸나
}
FirebaseAnalytics.getInstance(context).logEvent("home_loaded", params)
GA4 관리 화면에서 item_count를 커스텀 측정기준으로 등록했습니다. 며칠 기다렸다가 탐색 보고서를 열었습니다.
item_count 이벤트 수
(not set) 1,209
─────────────────────────────
합계 1,209
전량 (not set)이었습니다. 한 건도 값이 담기지 않았습니다. 이벤트 수 1,209는 정확했습니다. 즉 이벤트는 제대로 나가고 있었고, 값만 비어 있었습니다.
원인 — 측정기준은 문자열만 읽는다
GA4의 커스텀 측정기준(dimension)은 문자열 파라미터만 읽습니다. putLong·putDouble·putInt로 보낸 숫자는 측정기준으로 등록해 둬도 조회하면 전부 (not set)입니다.
GA4가 파라미터를 받는 창구는 두 개인데, 서로 통하지 않습니다.
| 커스텀 측정기준 (dimension) |
커스텀 측정항목 (metric) |
|
|---|---|---|
| 읽는 타입 | putString — 문자열만 |
putLong·putDouble — 숫자만 |
| 할 수 있는 것 | 값별로 쪼개서 센다 ("0건이 몇 번이었나") |
합계·평균을 낸다 ("총 몇 건이었나") |
| 못 하는 것 | 더하기 | 값별로 쪼개기 |
등록 화면에서는 이 구분이 눈에 잘 안 들어옵니다. 파라미터 이름을 적는 칸이 똑같이 생겼고, 타입이 안 맞아도 등록은 그냥 됩니다. 경고도 없습니다.
진짜 무서운 것은 실패하는 방식이다
이벤트 수가 맞게 나오니 대시보드는 멀쩡해 보입니다. 오류도, 경고도, 로그도 없습니다. 그 값을 보는 검사가 조용히 통과합니다.
저는 이걸 한참 뒤에 알았습니다. 하필 item_count를 쓰던 검사가 "목록이 빈 채로 화면이 떴는가"를 잡는 것이었습니다. 예전에 목록이 통째로 비어 나가는 사고가 있었고, 그 재발을 감지하려고 심어 둔 계측이었습니다.
그런데 값이 전부 (not set)이니 item_count = 0인 건이 한 건도 잡히지 않습니다. 검사는 매번 "이상 없음"을 반환했습니다. 그 검사는 한 번도 돈 적이 없었던 셈입니다.
제 앱들을 전수로 훑어봤더니 item_count를 보내는 앱 네 개가 전부 같은 상태였습니다. 한 곳을 잘못 짠 게 아니라, 같은 코드를 베껴 쓰면서 같은 오해가 그대로 복사돼 있었습니다.
해결 — 숫자를 구간 문자열로 바꿔 보낸다
값별로 쪼개서 세고 싶다면 숫자를 구간으로 묶은 문자열로 보냅니다.
// ❌ 측정기준으로는 영영 안 읽힌다
putLong("item_count", list.size.toLong())
// ✅ 구간 문자열
putString("item_count", bucketOf(list.size))
private fun bucketOf(n: Int): String = when {
n <= 0 -> "0" // ← 이게 잡고 싶었던 값이다
n < 10 -> "1_9"
n < 50 -> "10_49"
n < 100 -> "50_99"
else -> "100_plus"
}
구간을 나눌 때 가장 중요한 경계를 독립된 칸으로 두는 것이 핵심입니다. 저에게 필요했던 건 "0건"이었으므로 "0"을 따로 뺐습니다. "0_9"로 묶었다면 고쳐도 여전히 못 셌을 겁니다.
"0"·"1_9"·"100_plus"처럼 영숫자와 밑줄로 씁니다. 보고서에서 정렬이 문자열 순서라 "10_49"가 "1_9"보다 앞에 오는데, 자릿수를 맞추거나("01_09") 순서 접두를 붙이면("b_1_9") 읽기 편합니다.측정항목으로 등록하면 되는 것 아닌가
그것도 답이 아닙니다. 실제로 그렇게 해 봤는데, 커스텀 측정항목은 합계와 평균만 내줍니다. "총 3,400건"은 알려주지만 "0건이 몇 번이었나"는 못 셉니다. 제가 알고 싶었던 건 뒤쪽이었습니다.
둘 다 필요하면 둘 다 보내면 됩니다. 파라미터 이름만 다르게 해서 한 이벤트에 같이 싣습니다.
putString("item_count", bucketOf(n)) // 측정기준 — 구간별로 센다
putLong("item_total", n.toLong()) // 측정항목 — 합계·평균을 낸다
확인하는 법 — 코드를 읽지 말고 로그를 보세요
이 결함은 코드를 읽으면 정상으로 보입니다. 유닛테스트도 릴리스 빌드 확인도 잡아 주지 않습니다. 이벤트가 안 나가는 것도 아니고, 값이 틀린 것도 아니고, 타입이 안 맞을 뿐이기 때문입니다.
실기기에서 Firebase Analytics의 디버그 로그를 직접 보는 게 가장 확실합니다.
adb shell setprop log.tag.FA VERBOSE
adb shell setprop log.tag.FA-SVC VERBOSE
adb shell setprop debug.firebase.analytics.app 내.패키지.이름
adb logcat -d -s FA FA-SVC | findstr "Logging event"
파라미터가 타입까지 그대로 찍혀 나옵니다.
Logging event: origin=app, name=home_loaded,
params=Bundle[{source(_o)=cache, item_count(_o)=3}] ← 숫자로 나가는 중
params=Bundle[{source(_o)=cache, item_count(_o)=1_9}] ← 문자열로 고친 뒤
값이 따옴표 없이 3으로 찍히면 숫자, 1_9처럼 문자열 형태면 제대로 나가는 중입니다.
디버그 빌드는 난독화가 없어서 반쪽짜리 검증입니다. 실제로 저는 이 방법으로 캐시 경로가 이벤트를 통째로 건너뛰는 것도 같이 찾았습니다. 앱을 두 번 켜 봐야 보이는 종류였습니다 — 첫 실행은 네트워크로 가고, 두 번째부터 캐시를 타기 때문입니다.
같은 계열의 함정 하나 더
실패 원인을 보낼 때 예외 클래스 이름을 그대로 싣는 코드도 같은 방식으로 조용히 망가집니다.
// ❌ 릴리스에서 이름이 뭉개진다
putString("reason", e::class.simpleName ?: "unknown")
릴리스는 isMinifyEnabled = true라 라이브러리와 우리 예외의 클래스 이름이 한 글자로 줄어듭니다. 저는 보고서에서 reason=X를 봤습니다. 그날 무엇이 실패했는지는 영영 못 읽습니다. JDK 내장 예외는 난독화를 피해 살아남기 때문에 되는 앱과 안 되는 앱이 갈려 더 헷갈립니다. 게다가 그 한 글자는 빌드마다 바뀌어서 집계 자체가 성립하지 않습니다.
is 타입 검사로 고정 키를 만들어 보내야 합니다.
fun reasonOf(e: Throwable): String = when (e) {
is java.net.UnknownHostException -> "no_network"
is java.net.SocketTimeoutException -> "timeout"
is javax.net.ssl.SSLException -> "ssl"
is java.io.IOException -> "io"
else -> "unknown"
}
순서에 주의합니다. SocketTimeoutException은 IOException의 하위 타입이라 좁은 것을 먼저 놓지 않으면 전부 "io"로 뭉쳐집니다.
연결 실패 메시지에는 요청 URL이 통째로 실려 오는 경우가 있어서
serviceKey=·apiKey= 값이 평문으로 남습니다.정리
- GA4 커스텀 측정기준은 문자열만 읽습니다.
putLong으로 보낸 숫자는 등록해도 전부(not set)입니다. - 타입이 안 맞아도 등록은 되고 경고도 없습니다. 이벤트 수는 맞게 나와서 대시보드가 멀쩡해 보입니다.
- 값별로 세려면 구간 문자열로 보내고, 가장 중요한 경계는 독립된 칸으로 둡니다.
- 합계도 필요하면 측정항목용 숫자를 따로 하나 더 보냅니다.
- 검증은 실기기에서
adb logcat -s FA FA-SVC로 합니다. 코드를 읽어서는 안 보입니다. - 계측을 심은 것과 밖에서 읽히는 것은 다릅니다. GA4에서 그 값을 실제로 꺼내 보는 데까지가 한 작업입니다.
자주 묻는 것
등록하고 얼마나 기다려야 값이 보이나요?
커스텀 측정기준은 등록한 이후에 수집된 데이터부터 적용됩니다. 소급되지 않습니다. 보고서에 반영되기까지 보통 24-48시간 걸리므로, 하루 이틀은 (not set)이 정상입니다. 이 글의 증상은 며칠이 지나도 100%가 (not set)인 경우입니다. 급하게 확인하려면 DebugView를 쓰면 실시간으로 보입니다.
이벤트당 파라미터 개수 제한이 있나요?
이벤트 하나에 파라미터 25개까지이고, 속성 단위로 커스텀 측정기준은 이벤트 범위 50개, 측정항목은 50개까지 등록할 수 있습니다. 실무에서는 개수보다 이름을 앱마다 다르게 쓰는 것이 먼저 문제가 됩니다. 같은 사건이 앱마다 다른 파라미터 이름으로 들어오면 한 화면에서 못 묶습니다.
등록할 때 표시 이름은 아무렇게나 지어도 되나요?
표시 이름에 괄호를 넣지 마세요. 여러 개를 한 번에 등록할 때 괄호가 들어간 것만 조용히 빠지는 일을 겪었습니다. 오류 메시지는 나오지 않고, 등록 목록에서 하나가 없을 뿐입니다. 등록 직후에 목록을 다시 읽어 개수를 세어 보는 것을 권합니다.
