perf(otel/v2): resolve the caller's org only when a destination is org-scoped

The org fallback loads the team, which on a cache miss is a Prisma read on the authentication path. It cannot change which destinations are selected unless some access names an org, so it now runs only then; the logging credentials are also parsed once instead of twice.
This commit is contained in:
Yucheng Zhu 2026-08-04 23:50:30 -07:00
parent b5a51e72cd
commit d31b4a3fde
2 changed files with 57 additions and 16 deletions

View file

@ -149,7 +149,6 @@ _ENABLE_TEAM_STALE_ALIAS_BYPASS: bool | None = None
if TYPE_CHECKING:
from litellm.integrations.otel.model.destination import OtelDestination
from litellm.models.credentials import CredentialItem
from litellm.proxy.proxy_server import ProxyConfig as _ProxyConfig
from litellm.types.proxy.policy_engine import PolicyMatchContext
from litellm.types.utils import OtelDestinationParams
@ -751,6 +750,10 @@ async def _resolve_logging_exporters(
``access.global``). Each survivor is built via ``destination_for_credential`` and deduped on
(endpoint, headers, resource attributes). Returns ([], []) when nothing is selected
(default-deny).
The caller's org is resolved only when some destination is org-scoped: the fallback
loads the team, which on a cache miss is a Prisma read on the authentication path,
and it cannot change the outcome when no ``access`` names an org.
"""
from litellm.integrations.otel.model.config import is_otel_v2_enabled
from litellm.proxy.management_endpoints.logging_exporter_access import (
@ -766,26 +769,26 @@ async def _resolve_logging_exporters(
if not is_otel_v2_enabled():
return (), ()
if not any(
(info := parse_credential_info(credential.credential_info)) is not None and info.credential_type == "logging"
logging_credentials = tuple(
(credential, info)
for credential in litellm.credential_list
):
if (info := parse_credential_info(credential.credential_info)) is not None and info.credential_type == "logging"
)
if not logging_credentials:
return (), ()
team_id = user_api_key_dict.team_id
org_id = await _effective_org_id(user_api_key_dict)
org_id = (
await _effective_org_id(user_api_key_dict)
if any(info.access is not None and info.access.orgs for _, info in logging_credentials)
else None
)
team_ids, org_ids = identity_scope(team_id, org_id)
def _selected(credential: "CredentialItem") -> bool:
info = parse_credential_info(credential.credential_info)
if info is None or info.credential_type != "logging":
return False
return access_grants(info.access, team_ids, org_ids)
built = tuple(
result
for credential in litellm.credential_list
if _selected(credential)
for credential, info in logging_credentials
if access_grants(info.access, team_ids, org_ids)
if (result := destination_for_credential(credential)) is not None
)
deduped = {

View file

@ -6724,9 +6724,11 @@ async def test_resolve_logging_exporters_short_circuits_without_destinations(mon
@pytest.mark.asyncio
async def test_resolve_logging_exporters_runs_lookup_when_a_destination_exists(monkeypatch):
async def test_resolve_logging_exporters_resolves_a_global_destination_without_an_org_lookup(monkeypatch):
"""The short-circuit must not skip resolution when a destination exists: a global
destination is still resolved for a team-scoped key, and the org lookup runs."""
destination is still resolved for a team-scoped key. No destination is org-scoped
here, so the org fallback (a team load, and on a cache miss a Prisma read on the
auth path) cannot change the outcome and must not run."""
from litellm.integrations.otel.model.config import is_otel_v2_enabled
from litellm.proxy import litellm_pre_call_utils as pcu
@ -6755,10 +6757,46 @@ async def test_resolve_logging_exporters_runs_lookup_when_a_destination_exists(m
key = UserAPIKeyAuth(api_key="k", team_id="t1")
destinations, backends = await pcu._resolve_logging_exporters(key)
assert lookups["org"] == 1 # a destination exists, so the resolver runs the lookup
assert lookups["org"] == 0 # nothing is org-scoped, so the lookup is skipped
assert "generic" in backends # global access grants the team key
@pytest.mark.asyncio
async def test_resolve_logging_exporters_runs_the_org_lookup_for_an_org_scoped_destination(monkeypatch):
"""An org-scoped destination is invisible without the caller's org, so the fallback
still runs; a team key carrying no org_id resolves it through its team."""
from litellm.integrations.otel.model.config import is_otel_v2_enabled
from litellm.proxy import litellm_pre_call_utils as pcu
monkeypatch.setenv("LITELLM_OTEL_V2", "true")
is_otel_v2_enabled.cache_clear()
monkeypatch.setattr(
litellm,
"credential_list",
[
CredentialItem(
credential_name="d-org",
credential_values={"otel_endpoint": "https://collector/v1/traces"},
credential_info={"credential_type": "logging", "description": "generic", "access": {"orgs": ["o1"]}},
)
],
)
lookups = {"org": 0}
async def _spy_effective_org_id(user_api_key_dict):
lookups["org"] += 1
return "o1"
monkeypatch.setattr(pcu, "_effective_org_id", _spy_effective_org_id)
key = UserAPIKeyAuth(api_key="k", team_id="t1")
destinations, backends = await pcu._resolve_logging_exporters(key)
assert lookups["org"] == 1 # an org-scoped destination needs the caller's org
assert "generic" in backends # and the org grant then selects it
@pytest.mark.asyncio
async def test_resolve_logging_exporters_noop_when_flag_off(monkeypatch):
"""LITELLM_OTEL_V2 is the sole activation gate: with the flag off, the resolver