Merge pull request #34939 from BerriAI/litellm_dd_span_user_email
Some checks failed
ai-gateway image / ai-gateway release image (push) Has been cancelled
Publish basedpyright base counts / publish (push) Has been cancelled
Postgres Tests / schema-migration (push) Has been cancelled
CI Coverage / assert-ci-coverage (push) Has been cancelled
CodSpeed Benchmarks / benchmarks (push) Has been cancelled
UI Unit Tests / ui-unit-tests (push) Has been cancelled
GitHub Actions Security Analysis / zizmor (push) Has been cancelled
Code Quality Checks / code-quality (push) Has been cancelled
Code Quality Checks / python-310-import-smoke (push) Has been cancelled
Postgres Tests / proxy-security (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Postgres Tests / proxy-behavior (push) Has been cancelled
Unit Tests: Documentation Validation / documentation (push) Has been cancelled
Unit Tests / caching-local (push) Has been cancelled
Unit Tests / core-utils (push) Has been cancelled
Unit Tests / enterprise-package (push) Has been cancelled
Unit Tests / enterprise-routing (push) Has been cancelled
Unit Tests / integrations (push) Has been cancelled
Unit Tests / All Other Providers (push) Has been cancelled
Unit Tests / Vertex AI (push) Has been cancelled
Unit Tests / misc (push) Has been cancelled
Unit Tests / proxy-auth (push) Has been cancelled
Unit Tests / proxy-endpoints (push) Has been cancelled
Unit Tests / proxy-extras (push) Has been cancelled
Unit Tests / proxy-infra (push) Has been cancelled
Unit Tests / proxy-server (push) Has been cancelled
Unit Tests / responses-caching-types (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled

feat(dd_span_tagger): emit litellm.user_email span tag for JWT-authenticated requests
This commit is contained in:
Yassin Kortam 2026-09-15 12:40:19 -07:00 committed by GitHub
commit 252c71c0b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 2 deletions

View file

@ -36,11 +36,15 @@ class DDSpanTagger:
- ``litellm.key_alias`` human-readable alias for the API key
- ``litellm.key_hash`` hashed API key (safe to log; never the raw secret)
- ``litellm.requested_model`` model name as sent by the client
- ``litellm.user_email`` email of the authenticated user
Use cases:
- Trace all requests from a specific user/key: filter by ``litellm.key_alias`` or
``litellm.key_hash``.
- Trace all requests for a specific model: filter by ``litellm.requested_model``.
- Trace all requests from a specific person under JWT auth, where there is no key_alias:
filter by ``@litellm.user_email:"user@example.com"``. The value comes from the JWT claim
mapped by ``litellm_jwtauth.user_email_jwt_field``, or from the user row for virtual keys.
Note: key_alias / key_hash are not available for unauthenticated (e.g. 401) requests.
"""
@ -51,8 +55,10 @@ class DDSpanTagger:
set_active_span_tag("litellm.key_hash", str(user_api_key_dict.token))
if requested_model:
set_active_span_tag("litellm.requested_model", str(requested_model))
if user_api_key_dict.user_email:
set_active_span_tag("litellm.user_email", str(user_api_key_dict.user_email))
except Exception:
verbose_proxy_logger.debug(
"Failed to tag active ddtrace span with key/model tags",
"Failed to tag active ddtrace span with key/model/user tags",
exc_info=True,
)

View file

@ -3177,12 +3177,13 @@ class TestStreamingOverheadHeader:
class TestDDSpanTaggerTagRequest:
"""Tests for DDSpanTagger.tag_request - key/model DD span tagging."""
def _make_user_api_key_dict(self, key_alias=None, token=None):
def _make_user_api_key_dict(self, key_alias=None, token=None, user_email=None):
from litellm.proxy._types import UserAPIKeyAuth
d = UserAPIKeyAuth()
d.key_alias = key_alias
d.token = token
d.user_email = user_email
return d
def test_tags_key_alias_and_model(self):
@ -3223,6 +3224,30 @@ class TestDDSpanTaggerTagRequest:
mock_set_tag.assert_called_once_with("litellm.requested_model", "claude-3-5-sonnet")
def test_tags_user_email(self):
"""user_email is tagged so JWT-authenticated requests are traceable per person."""
user_key = self._make_user_api_key_dict(user_email="user@example.com")
with patch("litellm.proxy.dd_span_tagger.set_active_span_tag") as mock_set_tag:
DDSpanTagger.tag_request(
user_api_key_dict=user_key,
requested_model=None,
)
mock_set_tag.assert_called_once_with("litellm.user_email", "user@example.com")
def test_no_user_email_tag_when_absent(self):
"""No user email tag when the authenticated identity has no email."""
user_key = self._make_user_api_key_dict(key_alias="my-prod-key", user_email=None)
with patch("litellm.proxy.dd_span_tagger.set_active_span_tag") as mock_set_tag:
DDSpanTagger.tag_request(
user_api_key_dict=user_key,
requested_model="gpt-4o",
)
assert all(call.args[0] != "litellm.user_email" for call in mock_set_tag.call_args_list)
class TestHasAttributeErrorInChain:
"""Tests for _has_attribute_error_in_chain helper."""