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:
@@ -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()
|
||||
Reference in New Issue
Block a user