feat(diagnostics): active connect-failure triage + Sentry + in-app share (#2)
* feat(diagnostics): active connect-failure triage + Sentry + in-app share Replaces the opaque "Connection Failed" / "Network request failed" dead-end with on-device diagnostics that classify *why* a connect attempt failed. On failure (quick connect and edit-connection test), the app now: - runs parallel probes: target /global/health, target root, and a public 204 endpoint (internet reachability check) - classifies the cause: malformed-url, no-internet, server-unreachable, health-failed, tls-error, timeout - shows a plain-English summary + a "Share report" button that copies a full report (target URL, per-probe results w/ error.cause, device/app info, recent log ring-buffer) to the clipboard and opens the share sheet - captures the same structured context to Sentry (auto-upload), gated on EXPO_PUBLIC_SENTRY_DSN so dev/CI builds work without secrets New: src/lib/logbuffer.ts (ring buffer + logger), src/lib/diagnostics.ts (regex URL parse — Hermes URL is incomplete — probe + report + share), src/lib/sentry.ts (no-op-without-DSN wrapper, scrubs basic-auth from URLs). Wired Sentry.wrap around RootLayout and initSentry() at module load. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(sentry): wire Sentry DSN + source-map upload env into build; bump to 0.2.2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
src/lib/logbuffer.ts
Normal file
58
src/lib/logbuffer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
// In-memory ring buffer of recent log lines, for attaching to diagnostic reports.
|
||||
// Also mirrors to console so logs still show in Metro / logcat.
|
||||
|
||||
export type LogLevel = "debug" | "info" | "warn" | "error"
|
||||
|
||||
export interface LogEntry {
|
||||
ts: number
|
||||
level: LogLevel
|
||||
tag: string
|
||||
message: string
|
||||
}
|
||||
|
||||
const MAX_ENTRIES = 200
|
||||
const buffer: LogEntry[] = []
|
||||
|
||||
function push(level: LogLevel, tag: string, parts: unknown[]) {
|
||||
const message = parts
|
||||
.map((p) => {
|
||||
if (typeof p === "string") return p
|
||||
try {
|
||||
return JSON.stringify(p)
|
||||
} catch {
|
||||
return String(p)
|
||||
}
|
||||
})
|
||||
.join(" ")
|
||||
buffer.push({ ts: Date.now(), level, tag, message })
|
||||
if (buffer.length > MAX_ENTRIES) buffer.shift()
|
||||
|
||||
const line = `[${tag}] ${message}`
|
||||
if (level === "error") console.error(line)
|
||||
else if (level === "warn") console.warn(line)
|
||||
else console.log(line)
|
||||
}
|
||||
|
||||
export const log = {
|
||||
debug: (tag: string, ...parts: unknown[]) => push("debug", tag, parts),
|
||||
info: (tag: string, ...parts: unknown[]) => push("info", tag, parts),
|
||||
warn: (tag: string, ...parts: unknown[]) => push("warn", tag, parts),
|
||||
error: (tag: string, ...parts: unknown[]) => push("error", tag, parts),
|
||||
}
|
||||
|
||||
export function getLogEntries(): LogEntry[] {
|
||||
return [...buffer]
|
||||
}
|
||||
|
||||
export function formatLogLines(entries: LogEntry[] = buffer): string {
|
||||
return entries
|
||||
.map((e) => {
|
||||
const t = new Date(e.ts).toISOString().slice(11, 23)
|
||||
return `${t} ${e.level.toUpperCase().padEnd(5)} [${e.tag}] ${e.message}`
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
export function clearLog() {
|
||||
buffer.length = 0
|
||||
}
|
||||
Reference in New Issue
Block a user