Add Crashlytics BigQuery report script

Consulta el export de Crashlytics en BigQuery (dataset firebase_crashlytics,
proyecto portfoliojournal-ef2d7) con el service account de pass. Top crashes por
versión/build. Setup documentado en el docstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
2026-07-13 00:54:07 +02:00
parent 41e2a22a64
commit 8f14d4cd06
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
Crashlytics report for Portfolio Journal via the BigQuery export.
Setup (one-time, already done 2026-07-13):
- Firebase console → project portfoliojournal-ef2d7 → Integrations → BigQuery:
enable the Crashlytics export (requires Blaze plan).
- Grant the service account sirius@sirius-vazquez.iam BigQuery Data Viewer +
Job User on project portfoliojournal-ef2d7.
Creds come from `pass show google/sirius-service-account`. The export table is
created on the first crash after enabling, named like
`firebase_crashlytics.com_alexandrevazquez_PortfolioJournal_IOS`.
Usage: python3 Scripts/analyze_crashlytics.py [days] # default 30
"""
import subprocess, json, sys, urllib.request, urllib.error
PROJECT = "portfoliojournal-ef2d7"
DATASET = "firebase_crashlytics"
DAYS = int(sys.argv[1]) if len(sys.argv) > 1 else 30
def creds_token():
raw = subprocess.run(["pass", "show", "google/sirius-service-account"],
capture_output=True, text=True).stdout
try:
info = json.loads(raw)
except Exception:
out, ins, esc = [], False, False
for c in raw:
if esc: out.append(c); esc = False
elif c == "\\": out.append(c); esc = True
elif c == '"': ins = not ins; out.append(c)
elif ins and c == "\n": out.append("\\n")
elif ins and c == "\r": pass
else: out.append(c)
info = json.loads("".join(out))
from google.oauth2 import service_account
import google.auth.transport.requests
c = service_account.Credentials.from_service_account_info(
info, scopes=["https://www.googleapis.com/auth/cloud-platform"])
c.refresh(google.auth.transport.requests.Request())
return c.token
def api(token, path):
req = urllib.request.Request(
f"https://bigquery.googleapis.com/bigquery/v2/projects/{PROJECT}{path}",
headers={"Authorization": f"Bearer {token}"})
return json.load(urllib.request.urlopen(req))
def query(token, sql):
body = json.dumps({"query": sql, "useLegacySql": False, "timeoutMs": 60000}).encode()
req = urllib.request.Request(
f"https://bigquery.googleapis.com/bigquery/v2/projects/{PROJECT}/queries",
data=body, headers={"Authorization": f"Bearer {token}",
"Content-Type": "application/json"})
d = json.load(urllib.request.urlopen(req))
cols = [f["name"] for f in d.get("schema", {}).get("fields", [])]
rows = [[c.get("v") for c in r["f"]] for r in d.get("rows", [])]
return cols, rows
def main():
token = creds_token()
try:
td = api(token, f"/datasets/{DATASET}/tables?maxResults=100")
except urllib.error.HTTPError as e:
print(f"No access to {DATASET} (HTTP {e.code}).")
return
tables = [t["tableReference"]["tableId"] for t in td.get("tables", [])]
crash_tables = [t for t in tables if "IOS" in t or "ios" in t.lower()]
if not crash_tables:
print("No Crashlytics export tables yet — no crashes exported since the "
"export was enabled. (Good news.)")
return
table = crash_tables[0]
print(f"== Crashlytics {table} — últimos {DAYS}d ==\n")
sql = f"""
SELECT
issue_title, issue_subtitle,
application.display_version AS version,
COUNT(*) AS events,
COUNT(DISTINCT installation_uuid) AS users,
COUNTIF(error_type = 'FATAL') AS fatal
FROM `{PROJECT}.{DATASET}.{table}`
WHERE event_timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL {DAYS} DAY)
GROUP BY issue_title, issue_subtitle, version
ORDER BY events DESC
LIMIT 25
"""
cols, rows = query(token, sql)
if not rows:
print("Sin crashes en el periodo.")
return
for r in rows:
title, sub, ver, events, users, fatal = r
print(f"[{ver}] {title}{sub}")
print(f" {events} eventos · {users} usuarios · {fatal} fatales\n")
if __name__ == "__main__":
main()