Skip to content

feat(dd_span_tagger): emit litellm.user_email span tag for JWT-authenticated requests - #34939

Merged
yassin-berriai merged 2 commits into
litellm_internal_stagingfrom
litellm_dd_span_user_email
Sep 15, 2026
Merged

yassin-berriai merged 2 commits into
litellm_internal_stagingfrom
litellm_dd_span_user_email

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • JWT auth has no key_alias, so APM traces can't be filtered per user
  • DDSpanTagger.tag_request() only tagged key alias, key hash, model

How it solves it:

  • Tag the active span with litellm.user_email when the auth object has one
  • Value comes from the JWT claim mapped by user_email_jwt_field
  • Enables @litellm.user_email:"user@example.com" in Datadog

Relevant issues

Linear ticket

Resolves LIT-4880

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy on localhost:4000, JWT auth enabled with user_email_jwt_field: upn, real Anthropic calls (spend recorded, ~$0.000044 each), and ddtrace pointed at a local trace-agent stand-in on :8126 that decodes the msgpack payload and prints the span tags the Datadog backend would receive

Proxy launched with:

DATABASE_URL="postgresql://..." USE_DDTRACE=True DD_TRACE_AGENT_URL=http://127.0.0.1:8126 \
  JWT_PUBLIC_KEY_URL="http://localhost:8127/.well-known/jwks.json" JWT_AUDIENCE=litellm-proxy \
  uv run ddtrace-run python litellm/proxy/proxy_cli.py --config config.yaml --use_v2_migration_resolver

Relevant config:

general_settings:
  enable_jwt_auth: true
  litellm_jwtauth:
    user_id_jwt_field: sub
    user_email_jwt_field: upn
    user_id_upsert: true

Request signed by the local IdP, upn claim set to user@example.com:

curl -sS -X POST http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"model":"anthropic-haiku-4-5","messages":[{"role":"user","content":"reply with the word ok"}]}'

{"id":"chatcmpl-f566ad89-f783-4c32-8d71-9ae8747c1a16","model":"anthropic-haiku-4-5","choices":[{"finish_reason":"stop","index":0,"message":{"content":"ok","role":"assistant"}}], ...}

Before, at daf22ec871 (parent of the first commit; no user tag, so a JWT caller has nothing to filter APM on):

span: fastapi.request | resource: POST /v1/chat/completions
{
  "litellm.requested_model": "anthropic-haiku-4-5",
  "litellm.call_id": "b1da76ef-eb7d-4f22-afc4-1c66cd13d65c"
}

After, at 819faa01c3:

span: fastapi.request | resource: POST /v1/chat/completions
{
  "litellm.requested_model": "anthropic-haiku-4-5",
  "litellm.call_id": "79189884-e8fb-4fdf-8f1a-158078162085",
  "litellm.user_email": "user@example.com"
}

Type

🆕 New Feature

Changes

DDSpanTagger.tag_request() gains one tag:

if user_api_key_dict.user_email:
    set_active_span_tag("litellm.user_email", str(user_api_key_dict.user_email))

UserAPIKeyAuth.user_email is already populated on the JWT path from get_user_email (the claim named by litellm_jwtauth.user_email_jwt_field), and on the virtual-key path from the user row, so no plumbing was needed; the tag is simply absent when the authenticated identity has no email

The tag name is dotted to match the neighboring litellm.key_alias / litellm.requested_model tags; an earlier commit on this branch used litellm_user_email and was renamed for consistency

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/447287f0f7514672a528545ea1508abd

…ticated requests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds authenticated user email tagging to active Datadog request spans.

  • Emits litellm_user_email when UserAPIKeyAuth.user_email is populated.
  • Documents the new Datadog filtering capability.
  • Adds mocked unit coverage for present and absent email values.

Confidence Score: 5/5

The PR appears safe to merge with no actionable defects identified.

The optional authenticated email is forwarded through the existing span-tag helper only when populated, and the tests cover both branches without weakening existing coverage.

Important Files Changed

Filename Overview
litellm/proxy/dd_span_tagger.py Adds best-effort user-email span tagging while preserving existing behavior when no email is available.
tests/test_litellm/proxy/test_common_request_processing.py Adds focused mocked tests covering email-tag emission and omission.

Reviews (1): Last reviewed commit: "feat(dd_span_tagger): emit litellm_user_..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…stency

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat(dd_span_tagger): emit litellm_user_email span tag for JWT-authenticated requests feat(dd_span_tagger): emit litellm.user_email span tag for JWT-authenticated requests Jul 28, 2026
@codspeed

codspeed Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_dd_span_user_email (819faa0) with litellm_internal_staging (daf22ec)

Open in CodSpeed

@yuneng-berri
yuneng-berri deleted the branch litellm_internal_staging September 13, 2026 04:36
@yuneng-berri yuneng-berri reopened this Sep 13, 2026
@yassin-berriai
yassin-berriai merged commit 252c71c into litellm_internal_staging Sep 15, 2026
79 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_dd_span_user_email branch September 15, 2026 19:40
mateo-berri added a commit to mayank-affirm/litellm that referenced this pull request Sep 29, 2026
Rebuilds the branch on current main so the PR diff is only the Mantle overflow
mapping and its test. Drops the stale litellm_internal_staging drift (BerriAI#37762,
BerriAI#40993, BerriAI#34939) the branch had picked up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants