From 547213de7623effe59a48e64c63fa559f164fff5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 23 Mar 2026 19:15:06 -0700 Subject: [PATCH] fix(prometheus): replace private _labelnames access with owned _metric_label_registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _safe_labels previously read metric._labelnames, a private prometheus_client attribute. Instead, populate a _metric_label_registry dict (id(metric) → frozenset[labels]) inside _create_metric_factory at the time each metric is created, and look up that registry in _safe_labels. No private attributes accessed. --- litellm/integrations/prometheus.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 780642dc56e..d3283cc74e6 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -76,6 +76,10 @@ class PrometheusLogger(CustomLogger): # Always initialize label_filters, even for non-premium users self.label_filters = self._parse_prometheus_config() self._parse_exclude_config() + # Registry mapping id(metric) → frozenset of registered label names. + # Populated by _create_metric_factory; used by _safe_labels to avoid + # relying on prometheus_client private attributes. + self._metric_label_registry: Dict[int, frozenset] = {} # Create metric factory functions self._counter_factory = self._create_metric_factory(Counter) @@ -896,7 +900,12 @@ class PrometheusLogger(CustomLogger): metric_name = args[0] if args else kwargs.get("name", "") if self._is_metric_enabled(metric_name): - return metric_class(*args, **kwargs) + metric = metric_class(*args, **kwargs) + # Record label names so _safe_labels can filter without touching + # prometheus_client private attributes. + labelnames = kwargs.get("labelnames", args[2] if len(args) > 2 else []) + self._metric_label_registry[id(metric)] = frozenset(labelnames) + return metric else: return NoOpMetric() @@ -931,10 +940,14 @@ class PrometheusLogger(CustomLogger): When prometheus_exclude_labels strips labels at registration time, observation call sites must supply only the registered subset or prometheus_client raises ValueError (label count mismatch). + + Label names are looked up from _metric_label_registry (populated at creation + time by _create_metric_factory) to avoid relying on prometheus_client private + attributes. """ if isinstance(metric, NoOpMetric): return kwargs - registered = getattr(metric, "_labelnames", None) + registered = self._metric_label_registry.get(id(metric)) if registered is None: return kwargs return {k: v for k, v in kwargs.items() if k in registered}