1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
#!/usr/bin/env bash
# pull-crash-logs — Download recent Crashlytics crash reports via Firebase CLI
#
# Usage: ./scripts/pull-crash-logs [--limit N] [--app-id APP_ID] [--out DIR]
# Example: ./scripts/pull-crash-logs --limit 20 --out /tmp/crashes
#
# Requirements:
# - firebase-tools installed and authenticated (firebase login)
# - FIREBASE_PROJECT set in env, or edit PROJECT_ID below
# - firebase-admin or curl + gcloud token for REST fallback
set -euo pipefail
# ── Config ──────────────────────────────────────────────────────────────────
PROJECT_ID="${FIREBASE_PROJECT:-nav-test-c2872}"
LIMIT=10
OUT_DIR="$(pwd)/crash-logs"
# ── Arg parsing ─────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--limit) LIMIT="$2"; shift 2 ;;
--app-id) APP_ID="$2"; shift 2 ;;
--out) OUT_DIR="$2"; shift 2 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done
mkdir -p "$OUT_DIR"
echo "=== Firebase Crashlytics crash log pull ==="
echo " Project : $PROJECT_ID"
echo " Limit : $LIMIT"
echo " Output : $OUT_DIR"
echo ""
# ── Check tooling ────────────────────────────────────────────────────────────
require() { command -v "$1" &>/dev/null || { echo "ERROR: '$1' not found. Install it first." >&2; exit 1; }; }
# Prefer firebase CLI → fall back to REST via gcloud token
if command -v firebase &>/dev/null; then
MODE="firebase-cli"
elif command -v gcloud &>/dev/null; then
MODE="rest"
else
echo "ERROR: neither firebase-tools nor gcloud found." >&2
echo " Install firebase-tools: npm install -g firebase-tools" >&2
echo " Or gcloud SDK: https://cloud.google.com/sdk/docs/install" >&2
exit 1
fi
echo "Mode: $MODE"
echo ""
# ── Pull via firebase CLI ────────────────────────────────────────────────────
if [[ "$MODE" == "firebase-cli" ]]; then
# List Android apps in project to find app ID if not provided
if [[ -z "${APP_ID:-}" ]]; then
echo "Fetching app list..."
APP_ID=$(firebase apps:list --project "$PROJECT_ID" --json 2>/dev/null \
| python3 -c "
import json, sys
apps = json.load(sys.stdin)
android = [a for a in apps.get('result', []) if a.get('platform') == 'ANDROID']
if not android:
print('', end='')
else:
print(android[0]['appId'], end='')
" 2>/dev/null || true)
if [[ -z "$APP_ID" ]]; then
echo "WARNING: Could not auto-detect app ID. Set --app-id <id> manually." >&2
echo " Run: firebase apps:list --project $PROJECT_ID" >&2
else
echo " Auto-detected app ID: $APP_ID"
fi
fi
# Crashlytics data via firebase crashlytics:symbols or REST
# firebase CLI doesn't expose crash issue listing directly;
# use the REST API path below with a gcloud-obtained token.
echo ""
echo "NOTE: firebase CLI does not expose crash issue listing."
echo "Switching to REST API (requires gcloud auth)..."
MODE="rest"
fi
# ── Pull via Crashlytics REST API ────────────────────────────────────────────
if [[ "$MODE" == "rest" ]]; then
require curl
require python3
# Try gcloud first, then fall back to firebase-tools stored token
TOKEN=$(gcloud auth print-access-token 2>/dev/null) || TOKEN=""
if [[ -z "$TOKEN" ]]; then
FIREBASE_CONFIG="$HOME/.config/configstore/firebase-tools.json"
if [[ -f "$FIREBASE_CONFIG" ]]; then
TOKEN=$(python3 -c "
import json, sys
with open('$FIREBASE_CONFIG') as f:
d = json.load(f)
tokens = d.get('tokens', {})
print(tokens.get('access_token', ''), end='')
" 2>/dev/null)
fi
fi
if [[ -z "$TOKEN" ]]; then
echo "ERROR: Not authenticated. Run: firebase login --no-localhost" >&2
exit 1
fi
if [[ -z "${APP_ID:-}" ]]; then
echo "Fetching app list from Firebase Management API..."
APPS_JSON=$(curl -s \
-H "Authorization: Bearer $TOKEN" \
"https://firebase.googleapis.com/v1beta1/projects/${PROJECT_ID}/androidApps")
APP_ID=$(echo "$APPS_JSON" | python3 -c "
import json, sys
data = json.load(sys.stdin)
apps = data.get('apps', [])
if not apps:
print('', end='')
else:
print(apps[0]['appId'], end='')
" 2>/dev/null || true)
if [[ -z "$APP_ID" ]]; then
echo "ERROR: No Android apps found in project $PROJECT_ID" >&2
echo "Response: $APPS_JSON" >&2
exit 1
fi
echo " Auto-detected app ID: $APP_ID"
fi
# Crashlytics issue list via v1alpha1 API
ISSUES_URL="https://firebasecrashlytics.googleapis.com/v1alpha1/projects/${PROJECT_ID}/apps/${APP_ID}/issues?pageSize=${LIMIT}&orderBy=eventCount+desc"
echo ""
echo "Fetching top $LIMIT crash issues..."
ISSUES_FILE="$OUT_DIR/issues.json"
curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"$ISSUES_URL" \
-o "$ISSUES_FILE"
ISSUE_COUNT=$(python3 -c "
import json, sys
with open('$ISSUES_FILE') as f:
data = json.load(f)
issues = data.get('issues', [])
print(len(issues))
" 2>/dev/null || echo "0")
echo " Found $ISSUE_COUNT issues. Saved to $ISSUES_FILE"
echo ""
# Pretty-print summary
python3 -c "
import json, sys
with open('$ISSUES_FILE') as f:
data = json.load(f)
issues = data.get('issues', [])
if not issues:
print('No crash issues found (or API returned an error).')
print('Raw response:')
print(json.dumps(data, indent=2))
sys.exit(0)
print(f'{'ID':<30} {'EVENTS':>8} {'USERS':>7} TITLE')
print('-' * 90)
for issue in issues:
issue_id = issue.get('issueId', 'N/A')[:30]
title = issue.get('title', 'N/A')[:50]
event_cnt = issue.get('eventCount', '?')
user_cnt = issue.get('impactedUsersCount', '?')
print(f'{issue_id:<30} {str(event_cnt):>8} {str(user_cnt):>7} {title}')
"
# Pull individual events for each issue
echo ""
echo "Fetching events per issue..."
python3 -c "
import json, sys, subprocess, os
with open('$ISSUES_FILE') as f:
data = json.load(f)
issues = data.get('issues', [])
token = '$TOKEN'
project = '$PROJECT_ID'
app_id = '$APP_ID'
out_dir = '$OUT_DIR'
for issue in issues[:5]: # cap at 5 issues for detail pull
issue_id = issue.get('issueId', '')
if not issue_id:
continue
url = (f'https://firebasecrashlytics.googleapis.com/v1alpha1/'
f'projects/{project}/apps/{app_id}/issues/{issue_id}/events?pageSize=5')
out_file = os.path.join(out_dir, f'events_{issue_id[:20]}.json')
result = subprocess.run(
['curl', '-s', '-H', f'Authorization: Bearer {token}', url],
capture_output=True, text=True
)
with open(out_file, 'w') as f:
f.write(result.stdout)
try:
events = json.loads(result.stdout).get('events', [])
print(f' {issue_id[:30]}: {len(events)} events → {out_file}')
except Exception:
print(f' {issue_id[:30]}: parse error → {out_file}')
"
fi
echo ""
echo "Done. Files written to: $OUT_DIR"
ls -lh "$OUT_DIR"
|