{"record":{"id":"af075833fd6cb123","repo":"glanceapp/glance","slug":"fetching-stats-v","errorCode":null,"errorMessage":"fetching stats: %v","messagePattern":"fetching stats: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-dns-stats.go","lineNumber":532,"sourceCode":"\tvar topDomainsResponse topDomainsResponseJson\n\tvar topDomainsErr error\n\n\tif includeTopDomains {\n\t\ttopDomainsRequest, _ := http.NewRequestWithContext(ctx, \"GET\", instanceURL+\"/api/stats/top_domains?blocked=true\", nil)\n\t\ttopDomainsRequest.Header.Set(\"x-ftl-sid\", sessionID)\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttopDomainsResponse, topDomainsErr = decodeJsonFromRequest[topDomainsResponseJson](client, topDomainsRequest)\n\t\t}()\n\t}\n\n\twg.Wait()\n\tpartialContent := false\n\n\tif statsErr != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"fetching stats: %v\", statsErr)\n\t}\n\n\tif includeGraph && seriesErr != nil {\n\t\tslog.Error(\"Failed to fetch Pihole v6 graph data\", \"error\", seriesErr)\n\t\tpartialContent = true\n\t}\n\n\tif includeTopDomains && topDomainsErr != nil {\n\t\tslog.Error(\"Failed to fetch Pihole v6 top domains\", \"error\", topDomainsErr)\n\t\tpartialContent = true\n\t}\n\n\tstats := &dnsStats{\n\t\tTotalQueries:   statsResponse.Queries.Total,\n\t\tBlockedQueries: statsResponse.Queries.Blocked,\n\t\tBlockedPercent: int(statsResponse.Queries.PercentBlocked),\n\t\tDomainsBlocked: statsResponse.Gravity.DomainsBlocked,\n\t}","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-dns-stats.go#L514-L550","documentation":"Returned by the Pi-hole v6 stats fetcher after all parallel requests complete: the primary stats request (statsErr) failed, which is fatal — unlike the graph (series) and top-domains requests, whose failures only downgrade to errPartialContent. The wrapped error comes from decodeJsonFromRequest on the main stats endpoint.","triggerScenarios":"The main stats request (GET /api/stats with the session) returns non-2xx, times out, or returns undecodable JSON, while optional graph/top-domain goroutines may or may not have succeeded.","commonSituations":"Session invalidated server-side between auth and stats call; Pi-hole under load timing out; URL pointing to a path that 404s; proxy interference on /api/stats.","solutions":["Check the wrapped error for status vs transport: 401 means session/auth trouble, timeouts mean load/network.","curl -H 'X-Session: <sid>' {url}/api/stats after authenticating to verify the endpoint responds 200 JSON.","Ensure the widget url is the Pi-hole base URL (no /admin path suffix for v6 API).","If it happens only occasionally, rely on the widget's cache/retry on next page refresh."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"stats, sid, err := fetchPiholeV6Stats(...)\nif err != nil {\n    if errors.Is(err, errPartialContent) {\n        // graph/top-domains degraded but stats present: render with warning\n    } else {\n        // statsErr: fatal for this refresh; keep cached render and retry later\n    }\n}","preventionTips":["Use the widget's cache duration so transient stats failures serve stale data.","Watch for 401-wrapped causes — they indicate session/auth problems, not load.","Keep the Pi-hole FTL service healthy; its API is single-threaded under load."],"tags":["network","pihole","dns-stats","widget"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}