{"record":{"id":"ca97eee5d16433c6","repo":"thanos-io/thanos","slug":"empty-name-for-metric-family","errorCode":null,"errorMessage":"empty name for metric family","messagePattern":"empty name for metric family","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cortex/util/metrics_helper.go","lineNumber":77,"sourceCode":"\t\tout <- prometheus.MustNewConstMetric(desc, valueType, cr.Value, cr.LabelValues...)\n\t}\n}\n\n// MetricFamilyMap is a map of metric names to their family (metrics with same name, but different labels)\n// Keeping map of metric name to its family makes it easier to do searches later.\ntype MetricFamilyMap map[string]*dto.MetricFamily\n\n// NewMetricFamilyMap sorts output from Gatherer.Gather method into a map.\n// Gatherer.Gather specifies that there metric families are uniquely named, and we use that fact here.\n// If they are not, this method returns error.\nfunc NewMetricFamilyMap(metrics []*dto.MetricFamily) (MetricFamilyMap, error) {\n\tperMetricName := MetricFamilyMap{}\n\n\tfor _, m := range metrics {\n\t\tname := m.GetName()\n\t\t// these errors should never happen when passing Gatherer.Gather() output.\n\t\tif name == \"\" {\n\t\t\treturn nil, errors.New(\"empty name for metric family\")\n\t\t}\n\t\tif perMetricName[name] != nil {\n\t\t\treturn nil, fmt.Errorf(\"non-unique name for metric family: %q\", name)\n\t\t}\n\n\t\tperMetricName[name] = m\n\t}\n\n\treturn perMetricName, nil\n}\n\nfunc (mfm MetricFamilyMap) SumCounters(name string) float64 {\n\treturn sum(mfm[name], counterValue)\n}\n\nfunc (mfm MetricFamilyMap) SumGauges(name string) float64 {\n\treturn sum(mfm[name], gaugeValue)\n}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/internal/cortex/util/metrics_helper.go#L59-L95","documentation":"NewMetricFamilyMap builds a map from dto.MetricFamily pointers keyed by family name. Prometheus's Gather() output must never contain a family with an empty name; if it does, the helper refuses to build the map and returns this error as an internal invariant violation.","triggerScenarios":"Passing a []*dto.MetricFamily containing an entry whose GetName() is empty to NewMetricFamilyMap — typically via softRemoveUserRegistry or BuildMetricFamiliesPerUser on a registry whose gatherer produced a nameless family.","commonSituations":"Buggy custom collectors registering metrics without names; corrupt or hand-assembled MetricFamily slices; third-party collector implementations violating the prometheus contract.","solutions":["Audit registered Collectors for Collect methods that append MetricFamily values without setting Name.","Identify the offending collector by testing with only standard collectors enabled and bisecting.","Ensure metrics come from Gatherer.Gather() output rather than manually constructed slices.","Upgrade Prometheus client_golang / collector libraries to a version fixing the empty-name bug."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for _, mf := range families {\n    if mf.GetName() == \"\" {\n        return fmt.Errorf(\"metric family with empty name from collector\")\n    }\n}\nerr := util.NewMetricFamilyMap(families)","typeGuard":null,"tryCatchPattern":"mfm, err := util.NewMetricFamilyMap(metrics)\nif err != nil {\n    level.Error(logger).Log(\"msg\", \"gather produced invalid metric families\", \"err\", err)\n    return nil, err\n}","preventionTips":["Only feed NewMetricFamilyMap with Gatherer.Gather() output.","Test custom collectors with prometheus/client_golang's testutil.CollectAndCompare.","Never construct dto.MetricFamily slices by hand without setting Name."],"tags":["metrics","prometheus","invariant"],"backgroundTag":"internal-invariant-violation","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"}