{"record":{"id":"8b4bce2cb31d1b2b","repo":"thanos-io/thanos","slug":"parsing-metric-families-against-s","errorCode":null,"errorMessage":"parsing metric families against %s","messagePattern":"parsing metric families against (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/promclient/promclient.go","lineNumber":722,"sourceCode":"\n\treq, err := http.NewRequest(http.MethodGet, u.String(), nil)\n\tif err != nil {\n\t\treturn 0, errors.Wrap(err, \"create request\")\n\t}\n\n\tspan, ctx := tracing.StartSpan(ctx, \"/lowest_timestamp HTTP[client]\")\n\tdefer span.Finish()\n\n\tresp, err := c.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn 0, errors.Wrapf(err, \"request metric against %s\", u.String())\n\t}\n\tdefer runutil.ExhaustCloseWithLogOnErr(c.logger, resp.Body, \"request body\")\n\n\tparser := expfmt.NewTextParser(model.UTF8Validation)\n\tfamilies, err := parser.TextToMetricFamilies(resp.Body)\n\tif err != nil {\n\t\treturn 0, errors.Wrapf(err, \"parsing metric families against %s\", u.String())\n\t}\n\tmf, ok := families[\"prometheus_tsdb_lowest_timestamp_seconds\"]\n\tif !ok {\n\t\treturn 0, errors.Wrapf(err, \"metric families did not contain 'prometheus_tsdb_lowest_timestamp_seconds'\")\n\t}\n\tval := 1000 * mf.GetMetric()[0].GetGauge().GetValue()\n\n\t// in the case that we dont have cut a block yet, TSDB lowest timestamp is math.MaxInt64\n\t// but its represented as float and truncated so we need to do this weird comparison.\n\t// Since we use this for fan-out pruning we use min timestamp here to include this prometheus.\n\tif val == float64(math.MaxInt64) {\n\t\treturn math.MinInt64, nil\n\t}\n\treturn int64(val), nil\n}\n\nfunc formatTime(t time.Time) string {\n\treturn strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64)","sourceCodeStart":704,"sourceCodeEnd":740,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/promclient/promclient.go#L704-L740","documentation":"After fetching /metrics, LowestTimestamp parses the exposition text with expfmt.TextToMetricFamilies. If the body is not valid Prometheus text exposition format (truncated response, HTML error page, proxy interference, malformed metrics), the parse error is wrapped with \"parsing metric families against <url>\".","triggerScenarios":"parser.TextToMetricFamilies(resp.Body) fails because the response body is not valid Prometheus text format: an HTML login/error page from a reverse proxy, truncated body, gzip/compression mismatch, or invalid metric syntax.","commonSituations":"A proxy or auth gateway returns an HTML 200 page instead of metrics; Prometheus exposes protobuf format unexpectedly; middleboxes truncate large /metrics responses; non-Prometheus service listening on the configured port.","solutions":["curl the /metrics URL and confirm the body is Prometheus text exposition format, not HTML.","Check any reverse proxy/auth layer in front of Prometheus and exempt /metrics from HTML interstitials.","Verify no compression/content-encoding mismatch; ensure the client is not handing a gzip stream to the text parser un-decoded.","Confirm the port actually serves Prometheus, not another service."],"exampleFix":"// debugging aid: inspect what was actually returned\nresp, err := c.Do(req.WithContext(ctx))\nif err != nil { ... }\nbody, _ := io.ReadAll(resp.Body)\nif !bytes.HasPrefix(bytes.TrimSpace(body), []byte(\"#\")) {\n\treturn fmt.Errorf(\"/metrics at %s did not return exposition text: %.200s\", u.String(), body)\n}\nfamilies, err := parser.TextToMetricFamilies(bytes.NewReader(body))","handlingStrategy":"validation","validationCode":"resp, err := http.Get(metricsURL)\nif err != nil { return err }\nbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\nif !bytes.Contains(body, []byte(\"# HELP\")) && !bytes.Contains(body, []byte(\"# TYPE\")) {\n\treturn fmt.Errorf(\"%s does not serve Prometheus exposition text\", metricsURL)\n}","typeGuard":"func servesExpositionFormat(body []byte) bool {\n\treturn bytes.HasPrefix(bytes.TrimSpace(body), []byte(\"#\"))\n}","tryCatchPattern":null,"preventionTips":["Never put HTML-producing proxies/auth pages in front of /metrics without exempting it","Confirm with curl that the endpoint returns text exposition before wiring it up","Watch for middleboxes that truncate or re-encode metric responses"],"tags":["http","prometheus","metrics","parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}