{"record":{"id":"0eaaf134266950d1","repo":"amir20/dozzle","slug":"failed-to-fetch-logs-response-statustext","errorCode":null,"errorMessage":"Failed to fetch logs: ${response.statusText}","messagePattern":"Failed to fetch logs: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"assets/components/LogViewer/LogAnalytics.vue","lineNumber":137,"sourceCode":"const evaluating = ref(false);\nconst pageLimit = 1000;\nconst state = ref<\"downloading\" | \"ready\" | \"initializing\">(\"downloading\");\nconst bytes = ref(0);\nconst columns = ref<{ name: string; type: string }[]>([]);\nconst queryEl = useTemplateRef<HTMLTextAreaElement>(\"queryEl\");\n\nconst runQuery = ref(query.value);\nwatchDebounced(query, (v) => (runQuery.value = v), { debounce: 500 });\n\nconst url = withBase(\n  `/api/hosts/${container.host}/containers/${container.id}/logs?stdout=1&stderr=1&everything&jsonOnly`,\n);\n\nconst [{ useDuckDB }, response] = await Promise.all([import(`@/composable/duckdb`), fetch(url)]);\n\nif (!response.ok) {\n  console.log(\"error fetching logs from\", url);\n  throw new Error(`Failed to fetch logs: ${response.statusText}`);\n}\n\nconst { db, conn } = await useDuckDB();\nconst empty = await conn.query<Record<string, any>>(`SELECT 1 LIMIT 0`);\n\nonMounted(async () => {\n  try {\n    state.value = \"downloading\";\n\n    const reader = response.body?.getReader();\n    if (!reader) throw new Error(\"No reader available from stream\");\n\n    const chunks: Uint8Array[] = [];\n    bytes.value = 0;\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/assets/components/LogViewer/LogAnalytics.vue#L119-L155","documentation":"LogAnalytics.vue fetches the full log payload (for DuckDB analytics) and throws `Failed to fetch logs: ${response.statusText}` when response.ok is false. This is a manual guard: the fetch API itself does not throw on HTTP error statuses, so the component converts bad statuses into an exception that its catch block turns into an error UI state.","triggerScenarios":"Opening the log analytics view while the fetch(url) call (logs export endpoint for the container/host) returns non-2xx: container id no longer exists, host disconnected, 401 from expired session, or proxy 5xx during large log download.","commonSituations":"Large containers where the backend times out and a proxy returns 504; container recreated with a new id so the old URL 404s; auth middleware rejecting a stale JWT; running behind a load balancer with a short request timeout.","solutions":["Inspect the failing request's status code and body in DevTools Network tab","Re-authenticate if the status is 401/403, then reload the analytics view","Retry when the host/container is back online; verify the container id in the URL is current","Include response.status in the message since statusText can be empty on HTTP/2"],"exampleFix":"// before\nthrow new Error(`Failed to fetch logs: ${response.statusText}`);\n// after\nthrow new Error(`Failed to fetch logs: ${response.status} ${response.statusText || \"unknown error\"}`);","handlingStrategy":"try-catch","validationCode":"const head = await fetch(url, { method: \"HEAD\" });\nif (!head.ok) throw new Error(`Logs endpoint unavailable: ${head.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await loadAnalytics();\n} catch (e) {\n  state.value = \"error\";\n  errorMessage.value = e instanceof Error ? e.message : \"unknown\";\n}","preventionTips":["Verify the container id/host in the URL is current before opening analytics","Raise proxy timeouts for large log exports","Log response.status, not just statusText, for diagnosis"],"tags":["network","fetch","http-error","analytics"],"backgroundTag":"http-request-failed","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}