feat(prometheus): add opt-in stream label to litellm_proxy_total_requests_metric

Set prometheus_emit_stream_label: true in litellm_settings to emit a
stream label (True/False/None) on litellm_proxy_total_requests_metric.

Opt-in to avoid breaking cardinality on existing deployments.
This commit is contained in:
Ishaan Jaffer 2026-02-24 09:30:20 -08:00
parent bf7e636b95
commit 9873ad62fa
5 changed files with 157 additions and 3 deletions

View file

@ -122,7 +122,7 @@ Use this to track overall LiteLLM Proxy usage.
| Metric Name | Description |
|----------------------|--------------------------------------|
| `litellm_proxy_failed_requests_metric` | Total number of failed responses from proxy - the client did not get a success response from litellm proxy. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "user_email", "exception_status", "exception_class", "route", "model_id"` |
| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route", "model_id"` |
| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route", "model_id"`. Optionally includes `"stream"` — see [Emit Stream Label](#emit-stream-label). |
### Callback Logging Metrics
@ -214,9 +214,31 @@ litellm_settings:
```
### Emit Stream Label
Add a `stream` label to `litellm_proxy_total_requests_metric` to split requests by streaming vs. non-streaming. Disabled by default.
```yaml title="config.yaml"
litellm_settings:
callbacks: ["prometheus"]
prometheus_emit_stream_label: true
```
When enabled, `litellm_proxy_total_requests_metric` gains a `stream` label with values `"True"`, `"False"`, or `"None"`.
```
litellm_proxy_total_requests_metric{..., stream="True"} 42
litellm_proxy_total_requests_metric{..., stream="False"} 100
```
:::note
This label is opt-in because adding a new label to an existing metric changes its cardinality and breaks existing Prometheus queries / Grafana dashboards that target this metric. Enable it only on fresh deployments or when you are ready to update your dashboards.
:::
## [BETA] Custom Metrics
Track custom metrics on prometheus on all events mentioned above.
Track custom metrics on prometheus on all events mentioned above.
### Custom Metadata Labels

View file

@ -362,6 +362,7 @@ enable_end_user_cost_tracking_prometheus_only: Optional[bool] = None
custom_prometheus_metadata_labels: List[str] = []
custom_prometheus_tags: List[str] = []
prometheus_metrics_config: Optional[List] = None
prometheus_emit_stream_label: bool = False
disable_add_prefix_to_prompt: bool = (
False # used by anthropic, to disable adding prefix to prompt
)

View file

@ -966,6 +966,9 @@ class PrometheusLogger(CustomLogger):
),
client_ip=standard_logging_payload["metadata"].get("requester_ip_address"),
user_agent=standard_logging_payload["metadata"].get("user_agent"),
stream=str(standard_logging_payload.get("stream"))
if litellm.prometheus_emit_stream_label
else None,
)
if (
@ -1611,6 +1614,9 @@ class PrometheusLogger(CustomLogger):
client_ip=_metadata.get("requester_ip_address"),
user_agent=_metadata.get("user_agent"),
model_id=model_id,
stream=str(request_data.get("stream"))
if litellm.prometheus_emit_stream_label
else None,
)
_labels = prometheus_label_factory(
supported_enum_labels=self.get_labels_for_metric(

View file

@ -1,7 +1,7 @@
import re
from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Literal, Optional, Tuple
from typing import Any, Dict, List, Literal, Optional, Tuple
from pydantic import BaseModel, Field
from typing_extensions import Annotated
@ -41,6 +41,38 @@ def _sanitize_prometheus_label_name(label: str) -> str:
return sanitized
def _sanitize_prometheus_label_value(value: Optional[Any]) -> Optional[str]:
"""
Sanitize a label value for Prometheus text format compatibility.
Removes or replaces characters that break the Prometheus exposition format:
- U+2028 (Line Separator) and U+2029 (Paragraph Separator) are removed
- Carriage returns are removed
- Newlines are replaced with spaces
- Backslashes and double quotes are escaped per Prometheus spec
"""
if value is None:
return None
# Coerce non-string values (int, bool, etc.) to str before sanitizing
str_value: str = value if isinstance(value, str) else str(value)
# Remove Unicode line/paragraph separators that break text format
str_value = str_value.replace("\u2028", "").replace("\u2029", "")
# Remove carriage returns
str_value = str_value.replace("\r", "")
# Replace newlines with spaces
str_value = str_value.replace("\n", " ")
# Escape backslashes and double quotes per Prometheus exposition format
str_value = str_value.replace("\\", "\\\\").replace('"', '\\"')
return str_value
@dataclass
class MetricValidationError:
"""Error for invalid metric name"""
@ -153,6 +185,7 @@ class UserAPIKeyLabelNames(Enum):
CLIENT_IP = "client_ip"
USER_AGENT = "user_agent"
CALLBACK_NAME = "callback_name"
STREAM = "stream"
DEFINED_PROMETHEUS_METRICS = Literal[
@ -606,6 +639,14 @@ class PrometheusMetricLabels:
]
)
# Conditionally add stream label to litellm_proxy_total_requests_metric
if (
label_name == "litellm_proxy_total_requests_metric"
and litellm.prometheus_emit_stream_label is True
and UserAPIKeyLabelNames.STREAM.value not in default_labels
):
custom_labels.append(UserAPIKeyLabelNames.STREAM.value)
return default_labels + custom_labels
@ -677,6 +718,9 @@ class UserAPIKeyLabelValues(BaseModel):
user_agent: Annotated[
Optional[str], Field(..., alias=UserAPIKeyLabelNames.USER_AGENT.value)
] = None
stream: Annotated[
Optional[str], Field(..., alias=UserAPIKeyLabelNames.STREAM.value)
] = None
class PrometheusMetricsConfig(BaseModel):

View file

@ -0,0 +1,81 @@
"""
Unit tests for prometheus_emit_stream_label opt-in setting.
Tests that:
- stream label is NOT added to litellm_proxy_total_requests_metric by default
- stream label IS added when litellm.prometheus_emit_stream_label = True
- stream value is populated correctly from standard_logging_payload
"""
import pytest
import litellm
from litellm.types.integrations.prometheus import (
PrometheusMetricLabels,
UserAPIKeyLabelNames,
)
def test_stream_label_not_present_by_default():
"""stream label should NOT appear in litellm_proxy_total_requests_metric unless opted in"""
litellm.prometheus_emit_stream_label = False
labels = PrometheusMetricLabels.get_labels("litellm_proxy_total_requests_metric")
assert UserAPIKeyLabelNames.STREAM.value not in labels
def test_stream_label_present_when_opted_in():
"""stream label SHOULD appear in litellm_proxy_total_requests_metric when opted in"""
litellm.prometheus_emit_stream_label = True
try:
labels = PrometheusMetricLabels.get_labels("litellm_proxy_total_requests_metric")
assert UserAPIKeyLabelNames.STREAM.value in labels
finally:
litellm.prometheus_emit_stream_label = False
def test_stream_label_not_in_other_metrics_when_opted_in():
"""stream label should NOT be added to other metrics even when opted in"""
litellm.prometheus_emit_stream_label = True
try:
other_metrics = [
"litellm_proxy_failed_requests_metric",
"litellm_spend_metric",
"litellm_input_tokens_metric",
"litellm_output_tokens_metric",
"litellm_llm_api_latency_metric",
]
for metric in other_metrics:
labels = PrometheusMetricLabels.get_labels(metric)
assert UserAPIKeyLabelNames.STREAM.value not in labels, (
f"stream label should not be in {metric}"
)
finally:
litellm.prometheus_emit_stream_label = False
def test_stream_label_name():
"""STREAM label name should be 'stream'"""
assert UserAPIKeyLabelNames.STREAM.value == "stream"
def test_user_api_key_label_values_has_stream_field():
"""UserAPIKeyLabelValues should accept stream field"""
from litellm.types.integrations.prometheus import UserAPIKeyLabelValues
values = UserAPIKeyLabelValues(stream="True")
assert values.stream == "True"
values_false = UserAPIKeyLabelValues(stream="False")
assert values_false.stream == "False"
values_none = UserAPIKeyLabelValues()
assert values_none.stream is None
def test_stream_label_in_model_dump():
"""stream field appears in model_dump() output for use in prometheus_label_factory"""
from litellm.types.integrations.prometheus import UserAPIKeyLabelValues
values = UserAPIKeyLabelValues(stream="True")
dumped = values.model_dump()
assert "stream" in dumped
assert dumped["stream"] == "True"