This commit is contained in:
Syed Ahnaf Raza 2026-10-21 14:26:33 +00:00 • committed by GitHub
commit c3e375ec8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 0 deletions

View file

@ -117,6 +117,13 @@ class _FailureKwargsView(TypedDict, total=False):
_JSON_OBJECT: Final = TypeAdapter(Mapping[str, object], config=ConfigDict(hide_input_in_errors=True))
# Metadata keys dropped from spans when `litellm.redact_user_api_key_info` is set.
_USER_API_KEY_METADATA_KEYS: Final = frozenset(
key
for key in StandardLoggingMetadata.__required_keys__ | StandardLoggingMetadata.__optional_keys__
if key.startswith("user_api_key")
)
def _text_as_stored_on_span(span: Span, key: str, text: str) -> str:
from opentelemetry.sdk.trace import ReadableSpan
@ -1549,6 +1556,8 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger):
in ``standard_logging_object.metadata``; propagating that to every
span only adds noise that makes traces look mis-instrumented.
"""
if litellm.redact_user_api_key_info is True:
return
if team_id:
self.safe_set_attribute(
span=span,
@ -2457,7 +2466,10 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger):
metadata: Final = cast( # cast-ok: payload metadata follows the standard logging contract
StandardLoggingMetadata, standard_logging_payload["metadata"]
)
redact_user_api_key_info: Final = litellm.redact_user_api_key_info is True
for key, value in metadata.items():
if redact_user_api_key_info and key in _USER_API_KEY_METADATA_KEYS:
continue
self.safe_set_attribute(
span=span,
key=f"metadata.{key}",

View file

@ -0,0 +1,63 @@
"""Regression tests for ``redact_user_api_key_info`` in OTEL spans (#36758)."""
from unittest.mock import MagicMock
import pytest
import litellm
from litellm.integrations.opentelemetry import OpenTelemetry
_USER_API_KEY_METADATA = {
"user_api_key_hash": "hashed-secret",
"user_api_key_user_id": "uid-123",
"user_api_key_user_email": "user@example.com",
"user_api_key_team_id": "team-456",
}
def _metadata_attribute_keys(redact: bool, monkeypatch: pytest.MonkeyPatch) -> list[str]:
monkeypatch.setattr(litellm, "redact_user_api_key_info", redact)
otel_logger = OpenTelemetry()
otel_logger.safe_set_attribute = MagicMock()
otel_logger._set_inference_identity_attributes = MagicMock()
otel_logger._set_service_tier_attributes = MagicMock()
otel_logger._capture_in_span = MagicMock(return_value=False)
otel_logger.set_attributes(
span=MagicMock(),
kwargs={
"litellm_params": {"custom_llm_provider": "openai"},
"standard_logging_object": {
"call_type": "completion",
"metadata": {**_USER_API_KEY_METADATA, "generation_name": "test-gen"},
},
},
response_obj=None,
)
return [call.kwargs["key"] for call in otel_logger.safe_set_attribute.call_args_list]
def test_set_attributes_drops_user_api_key_metadata_when_redaction_enabled(monkeypatch):
attribute_keys = _metadata_attribute_keys(redact=True, monkeypatch=monkeypatch)
assert "metadata.generation_name" in attribute_keys
for key in _USER_API_KEY_METADATA:
assert f"metadata.{key}" not in attribute_keys
def test_set_attributes_keeps_user_api_key_metadata_when_redaction_disabled(monkeypatch):
attribute_keys = _metadata_attribute_keys(redact=False, monkeypatch=monkeypatch)
for key in _USER_API_KEY_METADATA:
assert f"metadata.{key}" in attribute_keys
@pytest.mark.parametrize("redact, expected_calls", [(True, 0), (False, 2)])
def test_team_attributes_on_child_spans_respect_redaction(monkeypatch, redact, expected_calls):
monkeypatch.setattr(litellm, "redact_user_api_key_info", redact)
otel_logger = OpenTelemetry()
otel_logger.safe_set_attribute = MagicMock()
otel_logger._set_team_attributes_on_span(span=MagicMock(), team_id="team-456", team_alias="team-alias")
assert otel_logger.safe_set_attribute.call_count == expected_calls