Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions litellm/types/integrations/prometheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def _sanitize_prometheus_label_value(value: Optional[Any]) -> Optional[str]:
append(ch)
return "".join(parts)

PROMETHEUS_METRICS_WILDCARD = "*"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wildcard rejected during initialization

When prometheus_metrics_config contains metrics: ["*"], PrometheusLogger.__init__ passes the wildcard through the existing metric-name validator, which rejects it because it is not in DEFINED_PROMETHEUS_METRICS and raises ValueError. The production parser also lacks the wildcard expansion and enablement handling asserted by these tests, so the advertised configuration cannot apply default labels.

Knowledge Base Used: Logging & Observability Integrations


@dataclass
class MetricValidationError:
Expand Down Expand Up @@ -775,7 +776,13 @@ class PrometheusMetricLabels:

@staticmethod
def get_labels(label_name: DEFINED_PROMETHEUS_METRICS) -> List[str]:
default_labels = getattr(PrometheusMetricLabels, label_name)
# Some entries in DEFINED_PROMETHEUS_METRICS (e.g. litellm_in_flight_requests)
# have no matching class attribute here, because that metric is created
# with a fixed, empty label set and never goes through get_labels_for_metric().
# Default to no labels instead of raising, so anything that legitimately
# iterates over every defined metric name (e.g. a wildcard config group)
# doesn't blow up on a metric like this.
default_labels = getattr(PrometheusMetricLabels, label_name, [])
custom_labels = []

# Add custom metadata labels
Expand Down Expand Up @@ -836,7 +843,6 @@ def get_labels(label_name: DEFINED_PROMETHEUS_METRICS) -> List[str]:
"api_key_hash": "hashed_api_key",
}


@dataclass(frozen=True, init=False)
class UserAPIKeyLabelValues:
"""
Expand Down Expand Up @@ -933,7 +939,17 @@ def model_dump(self) -> Dict[str, Any]:

@dataclass
class PrometheusMetricsConfig:
"""Configuration for filtering Prometheus metrics (parsed once from proxy config)."""
"""
Configuration for filtering Prometheus metrics (parsed once from proxy config).

`metrics` is normally a list of names from DEFINED_PROMETHEUS_METRICS. It
can also be `[PROMETHEUS_METRICS_WILDCARD]` ("*") to apply `include_labels`
as the default label set for every metric, instead of listing all of them.
A wildcard group only sets defaults - it never disables a metric, and a
group naming a specific metric still wins over the wildcard for that one
metric. A group can't mix the wildcard with real metric names; use two
groups instead (see PrometheusLogger._build_label_filters).
"""

group: str
metrics: List[str]
Expand Down
154 changes: 154 additions & 0 deletions tests/test_litellm/integrations/test_prometheus_wildcard_labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import sys
from typing import get_args

import pytest

sys.path.insert(0, "../../../..")

import litellm
from litellm.integrations.prometheus import PrometheusLogger
from litellm.types.integrations.prometheus import (
DEFINED_PROMETHEUS_METRICS,
PROMETHEUS_METRICS_WILDCARD,
PrometheusMetricsConfig,
)


def _bare_logger() -> PrometheusLogger:
"""A PrometheusLogger instance with __init__ skipped, for testing the
config-parsing helpers in isolation without re-registering ~70 real
metrics against prometheus_client's global registry on every test."""
return PrometheusLogger.__new__(PrometheusLogger)


def test_wildcard_sets_default_labels_for_every_metric():
logger = _bare_logger()
configs = [
PrometheusMetricsConfig(
group="defaults",
metrics=[PROMETHEUS_METRICS_WILDCARD],
include_labels=["hashed_api_key", "team"],
)
]

label_filters = logger._build_label_filters(configs)

assert set(label_filters.keys()) == set(get_args(DEFINED_PROMETHEUS_METRICS))
for labels in label_filters.values():
assert labels == ["hashed_api_key", "team"]


def test_named_group_overrides_wildcard_for_that_metric():
logger = _bare_logger()
configs = [
PrometheusMetricsConfig(
group="defaults",
metrics=[PROMETHEUS_METRICS_WILDCARD],
include_labels=["hashed_api_key", "team"],
),
PrometheusMetricsConfig(
group="spend_needs_more",
metrics=["litellm_spend_metric"],
include_labels=["hashed_api_key", "team", "end_user"],
),
]

label_filters = logger._build_label_filters(configs)

assert label_filters["litellm_spend_metric"] == [
"hashed_api_key",
"team",
"end_user",
]
assert label_filters["litellm_total_tokens_metric"] == ["hashed_api_key", "team"]


def test_wildcard_group_does_not_disable_other_metrics():
"""A wildcard-only config should leave every metric enabled - it's a
label filter, not an allowlist."""
logger = _bare_logger()
litellm.prometheus_metrics_config = [
{
"group": "defaults",
"metrics": [PROMETHEUS_METRICS_WILDCARD],
"include_labels": ["team"],
}
]

Comment thread
ademicho123 marked this conversation as resolved.
Outdated
logger._parse_prometheus_config()

assert logger.enabled_metrics == set()
assert logger._is_metric_enabled("litellm_spend_metric") is True
assert logger._is_metric_enabled("litellm_mcp_tool_calls_total") is True


def test_wildcard_combined_with_named_enable_list():
"""Wildcard for labels + a separate group naming which metrics are
enabled - the two concerns are independent."""
logger = _bare_logger()
litellm.prometheus_metrics_config = [
{
"group": "enabled",
"metrics": ["litellm_spend_metric", "litellm_total_tokens_metric"],
},
{
"group": "defaults",
"metrics": [PROMETHEUS_METRICS_WILDCARD],
"include_labels": ["team"],
},
]

label_filters = logger._parse_prometheus_config()

assert logger.enabled_metrics == {
"litellm_spend_metric",
"litellm_total_tokens_metric",
}
assert logger._is_metric_enabled("litellm_spend_metric") is True
assert logger._is_metric_enabled("litellm_cache_hits_metric") is False
assert label_filters["litellm_spend_metric"] == ["team"]


def test_wildcard_mixed_with_named_metric_in_same_group_raises():
logger = _bare_logger()
configs = [
PrometheusMetricsConfig(
group="broken",
metrics=[PROMETHEUS_METRICS_WILDCARD, "litellm_spend_metric"],
include_labels=["team"],
)
]

with pytest.raises(ValueError, match="mixes the wildcard"):
logger._validate_all_configurations(configs)


def test_wildcard_with_unknown_label_is_reported():
logger = _bare_logger()
configs = [
PrometheusMetricsConfig(
group="typo",
metrics=[PROMETHEUS_METRICS_WILDCARD],
include_labels=["taem"], # typo, should be "team"
)
]

results = logger._validate_all_configurations(configs)

assert results.has_errors
assert any("taem" in err.message for err in results.label_errors)


def test_wildcard_with_no_include_labels_is_a_noop():
logger = _bare_logger()
configs = [
PrometheusMetricsConfig(
group="pointless",
metrics=[PROMETHEUS_METRICS_WILDCARD],
include_labels=None,
)
]

label_filters = logger._build_label_filters(configs)

assert label_filters == {}
Loading