Ë
    tPêjàI  ã                   ó8  — U d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	 ddl
mZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZmZ ddlmZmZmZ ddlmZm Z m!Z! ddl"m#Z# dZ$ee%d<   dZ&ee%d<   dZ'ee%d<   dZ(ee%d<   e)e)e*e*f   df   Z+ee%d<   e)e+e+e*dz  e*dz  f   Z,ee%d<    e	i «      Z-eee*e*f      e%d<   eZ.ee%d<   dee*e*f   dz  de*dz  fd „Z/d!eddfd"„Z0d#ee*e*f   de*fd$„Z1d#ee*e*f   de*fd%„Z2 ed&d&¬'«       G d(„ d)«      «       Z3 G d*„ d+«      Z4y),a€  Per-request multi-tenant tracer routing.

When a request carries team/key vendor credentials in
``standard_callback_dynamic_params``, or the key/team config resolved at auth
names a destination project or a service name, its spans must export through a
``TracerProvider`` whose OTLP headers carry those credentials / that project,
or whose Resource carries that ``service.name``. ``TenantTracerCache`` builds
and caches one provider per distinct (credentials, project, service name)
tuple, and otherwise hands back the logger's default tracer. This lets a
single logger fan requests out to many tenants without needing a logger per
tenant.
é    N)ÚOrderedDict)ÚMapping)Ú	dataclass)ÚMappingProxyType)ÚFinalÚ	TypeAlias©Úquote)ÚTracerProvider)ÚTracer)Úverbose_logger)ÚOTEL_SERVICE_NAME_METADATA_KEYS)ÚExporterSpecÚOpenTelemetryV2Config)Úbuild_tracer_providerÚexporter_transportÚ
get_tracer)Údynamic_otlp_endpointÚdynamic_otlp_headersÚproject_routing_headers)ÚStandardCallbackDynamicParams)ÚconsoleÚ	in_memoryÚinmemoryÚmemoryÚ_NON_OTLP_KINDS)Ú	otlp_grpcÚgrpcÚ_GRPC_KINDSé   Ú_MAX_CACHED_PROVIDERSé@   Ú_MAX_RETIRED_PROVIDERS.Ú_HeaderItemsÚ	_RouteKeyÚ_NO_HEADERSÚ_SERVICE_NAME_KEYSÚauth_metadataÚreturnc                 ó@   ‡ ‡— ‰ syt        ˆ ˆfd„t        D «       d«      S )z‘The per-request ``service.name`` override for this key/team, if any.

    ``None`` keeps the env-configured default (``OTEL_SERVICE_NAME``).
    Nc              3   ón   •K  — | ],  }‰j                  |«      xs d j                  «       xŠsŒ)‰–— Œ. y­w)Ú N)ÚgetÚstrip)Ú.0Úkeyr(   Ústrippeds     €€úo/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/integrations/otel/plumbing/routing.pyú	<genexpr>z&tenant_service_name.<locals>.<genexpr>[   s6   øè ø€ Òh�cÀ×ARÑARÐSVÓAWÒA]Ð[]×@dÑ@dÓ@fÐ4f°HÑ4fŒÑhùs   ƒ*5®5)Únextr'   )r(   r1   s   `@r2   Útenant_service_namer5   S   s$   ù€ ñ
 ØÜÜhÔ/ÔhØóð ó    Úproviderc                 ó~   — 	 | j                  «        y# t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)ad  Flush + stop an evicted provider's processors (reclaims their threads).

    ``TracerProvider.shutdown`` force-flushes each ``SpanProcessor`` before
    stopping it, so any spans already handed to a ``BatchSpanProcessor`` are
    exported rather than dropped. Best-effort: a shutdown failure must not break
    the request that triggered the eviction.
    z1OTel V2: error shutting down evicted provider: %sN)ÚshutdownÚ	Exceptionr   Údebug)r7   Úes     r2   Ú_shutdown_providerr=   `   s;   € ðUØ×ÑÕøÜò UÜ×ÑÐPÐRS×TÑTûðUús   ‚ “	<œ7·<Úheadersc                 óN   — dj                  d„ | j                  «       D «       «      S )Nú,c              3   ó0   K  — | ]  \  }}|› d |› �–— Œ y­w)ú=N© ©r/   r0   Úvalues      r2   r3   z'_plain_header_string.<locals>.<genexpr>o   s   è ø€ ÒG©¨¨e�s�e˜1˜U˜GÔ$ÑGùs   ‚©ÚjoinÚitems©r>   s    r2   Ú_plain_header_stringrJ   n   s   € Ø�8‰8ÑG°w·}±}³ÔGÓGÐGr6   c                 óN   — dj                  d„ | j                  «       D «       «      S )zŸPercent-encode values so one containing the ``k=v,k=v`` separators (e.g.
    a project name with a comma) survives; ``parse_env_headers`` decodes it back.
    r@   c              3   óF   K  — | ]  \  }}|› d t        |d¬«      › �–— Œ y­w)rB   r,   )ÚsafeNr	   rD   s      r2   r3   z)_encoded_header_string.<locals>.<genexpr>v   s'   è ø€ ÒW¹¸¸e�s�e˜1œU 5¨rÔ2Ð3Ô4ÑWùs   ‚!rF   rI   s    r2   Ú_encoded_header_stringrN   r   s   € ð �8‰8ÑWÀwÇ}Á}ÃÔWÓWÐWr6   T)ÚfrozenÚslotsc                   ó:   — e Zd ZU dZeed<   eed<   dZedz  ed<   y)ÚTenantRoutea+  The tracer to create a span on, plus whether it must root its own trace.

    ``detached`` is True when the routed span exports to a DIFFERENT backend
    than the request's root span, which always exports through the default
    tracer. A detached span roots a fresh trace with a link back to the request
    trace for correlation, so the destination account is not left holding a
    child whose parent it never received. It is driven by whether routing
    headers were actually applied to an owned exporter, not merely requested:
    a credential or project route whose callback owns no exporter those headers
    can reach exports through the default backend unchanged, so it stays
    parented like an unrouted span.

    Credential routing (a team/key's own vendor account) is one detaching case:
    the root, auth, and db spans stay on the operator's default backend while
    the LLM-call span exports to the tenant's account, so parenting it into the
    request trace makes the tenant account show a fragmented span with a missing
    parent. Project routing (Phoenix) is the other: Phoenix assigns a whole
    trace to one project by whichever of its spans arrives first, so a
    project-routed span parented into the request trace gets dragged into the
    project of the default-exported request spans and the header does nothing.
    Both mirror the v1 loggers, which exported each request under its own
    backend-local root. Service-name routing does NOT detach: it relabels
    ``service.name`` on the SAME operator backend, where the parent is present.
    ÚtracerÚdetachedNr7   )	Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   Ú__annotations__Úboolr7   r   rC   r6   r2   rR   rR   y   s$   … ñð2 ƒNØƒNð
 '+€Hˆn˜tÑ#Ô*r6   rR   c                   óŽ  — e Zd ZdZdededz  deddfd„Zdedz  ddfd	„Z	 dd
e	de
dz  deeef   dz  defd„Zdedeeef   deeef   dedz  dedz  defd„Zdedz  fd„Zde
dz  deeef   fd„Zdeeef   dz  deeef   fd„Z	 	 ddeeef   deeef   dedz  dedz  def
d„Z	 ddedeeef   deeef   dedz  def
d„Zy)ÚTenantTracerCachezQTenant-scoped ``TracerProvider`` cache keyed by routing headers and service name.ÚconfigÚcallback_nameNÚtracer_namer)   c                 ó4  ‡— || _         ‰| _        || _        t        j                  «       | _        t        «       | _        i | _        t        «       | _	        t        ˆfd„|j                  D «       «      }d|v | _        d|v xs d|v | _        d| _        d| _        y )Nc              3   óh   •K  — | ])  }|j                   ‰k(  sŒt        |j                  «      –— Œ+ y ­w©N)Úownerr   Úkind)r/   Úspecr^   s     €r2   r3   z-TenantTracerCache.__init__.<locals>.<genexpr>º   s,   øè ø€ ò (
Ø.2È$Ï*É*ÐXeÓJeÔ˜tŸy™y×)ñ(
ùs   ƒ2˜2Úhttpr   F)Ú_configÚ_callback_nameÚ_tracer_nameÚ	threadingÚLockÚ_lockr   Ú
_providersÚ_open_span_countsÚ_retiredÚtupleÚ	exportersÚ_project_routableÚ_credential_routableÚ_warned_project_unroutableÚ_warned_credential_unroutable)Úselfr]   r^   r_   Úowned_transportss     `  r2   Ú__init__zTenantTracerCache.__init__    s¤   ø€ ð ˆŒØ+ˆÔØ'ˆÔô &ŸN™NÓ,ˆŒ
ä‹Mð 	Œð =?ˆÔä;F»=ˆŒô #(ó (
Ø6<×6FÑ6Fô(
ó #
Ðð "(Ð+;Ð!;ˆÔØ$*Ð.>Ð$>Ò$\À&ÐL\ÐB\ˆÔ!Ø*/ˆÔ'Ø-2ˆÕ*r6   r7   c                 ón  — |€y| j                   5  | j                  j                  |d«      dz
  }|dkD  r|| j                  |<   	 ddd«       y| j                  j                  |d«       || j                  v }| j                  j                  |d«       ddd«       rt        |«       yy# 1 sw Y   ŒxY w)a]  Drop one open-span count; shut a retired provider down once drained.

        ``None`` (the default route) is a no-op so callers can release a
        ``TenantRoute.provider`` unconditionally. The shutdown itself runs
        outside the lock: it force-flushes over the network and must not stall
        every concurrently routing request.
        Nr   é   )rl   rn   r-   Úpopro   r=   )rv   r7   Ú	remainingÚdraineds       r2   ÚreleasezTenantTracerCache.releaseÂ   s¶   € ð ÐØØ�Z‰Zñ 	.Ø#×5Ñ5×9Ñ9¸(ÀAÓFÈÑJˆIØ˜1Š}Ø3<�×&Ñ& xÑ0Ø÷		.ð 	.ð
 ×"Ñ"×&Ñ& x°Ô6Ø%¨¯©Ð6ˆGØ�M‰M×Ñ˜h¨Ô-÷	.ñ Ü˜xÕ(ð ÷	.ð 	.ús   �5B+ÁAB+Â+B4ÚdefaultÚdynamic_paramsr(   c                 ó¨  — | j                  |«      }| j                  |«      }t        |«      }|s|s|€t        |d¬«      S t	        | j
                  |«      }t        t        |j                  «       «      «      t        t        |j                  «       «      «      ||f}| j                  5  | j                  |||||«      }	| j                  j                  |	d«      dz   | j                  |	<   | j                  «       }
ddd«       
�t        |
«       t        t        	| j                   «      t#        |«      xs t#        |«      |	¬«      S # 1 sw Y   ŒNxY w)uà  Return the tracer (and trace-detachment flag) for this request.

        Use ``default`` unless the request's dynamic credentials, its key/team
        project, or its key/team service name require a scoped tracer, in
        which case build (or reuse) one. The cache is a bounded LRU: the
        least-recently-used provider is flushed and shut down on overflow so
        its exporter threads don't accumulate.

        A routed provider is returned already held â€” its open-span count is
        incremented in the same critical section as the cache update â€” so a
        concurrent overflow eviction can't shut it down between selection and
        the caller's span start. The caller must ``release`` it exactly once.
        NF)rS   rT   r   rz   )rS   rT   r7   )Ú_credential_headersÚ_project_headersr5   rR   r   rh   rp   ÚsortedrH   rl   Ú_cached_provider_lockedrn   r-   Ú_evicted_on_overflow_lockedr=   r   ri   rZ   )rv   r   r€   r(   Úcredential_headersÚproject_headersÚservice_nameÚendpointÚ	cache_keyr7   Úevicteds              r2   Ú	route_forzTenantTracerCache.route_for×   sO  € ð& %)×$<Ñ$<¸^Ó$LÐØ!%×!6Ñ!6°}Ó!EˆÜ1°-Ó@ˆÙ!©/¸lÐ>RÜ g¸Ô>Ð>ô 0°×0CÑ0CÀ^ÓTˆä”&Ð+×1Ñ1Ó3Ó4Ó5Ü”&˜×.Ñ.Ó0Ó1Ó2ØØð	
ˆ	ð �Z‰Zñ 	@Ø"×:Ñ:ØÐ-¨ÀÈ,óˆHð 04×/EÑ/E×/IÑ/IÈ(ÐTUÓ/VÐYZÑ/ZˆD×"Ñ" 8Ñ,Ø!×=Ñ=Ó?ˆG÷	@ð ÐÜ˜wÔ'ÜÜ˜h¨×(9Ñ(9Ó:Ü˜/Ó*ÒF¬dÐ3EÓ.FØô
ð 	
÷	@ð 	@ús   Â)AEÅEr‹   r‡   rˆ   rŠ   r‰   c                 óÒ   — | j                   j                  |«      }|�| j                   j                  |«       |S t        | j	                  ||||«      «      }|| j                   |<   |S rb   )rm   r-   Úmove_to_endr   Ú_routed_config)rv   r‹   r‡   rˆ   rŠ   r‰   ÚcachedÚbuilts           r2   r…   z)TenantTracerCache._cached_provider_locked  si   € ð Ÿ™×+Ñ+¨IÓ6ˆØÐØ�O‰O×'Ñ'¨	Ô2ØˆMÜ,Ø×ÑÐ 2°OÀXÈ|Ó\ó
ˆð &+ˆ�‰˜	Ñ"Øˆr6   c                 óŽ  — t        | j                  «      t        k  ry| j                  j                  d¬«      \  }}| j                  j                  |d«      dk(  r|S d| j                  |<   t        | j                  «      t        k  ry| j                  j                  d¬«      \  }}| j                  j                  |d«       |S )a  Pop the LRU provider past the cap; return it if the caller must shut it down.

        A provider with open spans is retired to drain instead: stopping its
        processors while a span opened at ``pre_call`` is still live would
        silently drop that span at end instead of exporting it. Retirees are
        themselves capped, so the stalest one is shut down (and its open-span
        count dropped, making its eventual ``release`` a no-op) once too many
        pile up rather than letting them accumulate a thread each.
        NF)Úlastr   )	Úlenrm   r!   Úpopitemrn   r-   ro   r#   r{   )rv   Ú_rŒ   Ú
overfloweds       r2   r†   z-TenantTracerCache._evicted_on_overflow_locked  s®   € ô ˆt�‰ÓÔ#8Ò8ØØ—_‘_×,Ñ,°%Ð,Ó8‰
ˆˆ7Ø×!Ñ!×%Ñ% g¨qÓ1°QÒ6ØˆNØ!%ˆ�‰�gÑÜˆt�}‰}ÓÔ!7Ò7ØØŸ™×-Ñ-°5Ð-Ó9‰ˆ
�AØ×Ñ×"Ñ" :¨tÔ4ØÐr6   c                 óÐ   — t        | j                  |«      xs t        }|r| j                  r|S | j                  s'd| _        t        j                  d| j                  «       t        S )a„  The per-request dynamic OTLP credentials, if this cache can apply them.

        A callback owning only a console/in_memory exporter has nowhere to stamp
        them, so the span would export to the operator's default backend
        unchanged; routing there and detaching would orphan it on the very
        backend that holds its parent. Warn once and keep the default tracer.
        Tz�OTel V2: %s request carries dynamic credentials, but the callback owns no OTLP exporter to stamp them onto; spans export to the default backend.)r   rh   r&   rs   ru   r   Úwarning)rv   r€   Ú	requesteds      r2   r‚   z%TenantTracerCache._credential_headers.  sc   € ô 0°×0CÑ0CÀ^ÓTÒcÔXcˆ	Ù˜D×5Ò5ØÐØ×1Ò1Ø15ˆDÔ.Ü×"Ñ"ðYà×#Ñ#ôô
 Ðr6   c                 óÀ   — t        | j                  |«      }|r| j                  r|S | j                  s'd| _        t	        j
                  d| j                  «       t        S )zïThe per-request project-routing headers, if this cache can apply them.

        A gRPC-only exporter can't (the project header route is HTTP-only), so
        the request warns once and stays on the env-configured default project.
        Tz¢OTel V2: %s key/team config names a per-request project, but its exporter is not OTLP/HTTP and the project header is HTTP-only; spans stay in the default project.)r   rh   rr   rt   r   rš   r&   )rv   r(   r›   s      r2   rƒ   z"TenantTracerCache._project_headersB  s^   € ô 3°4×3FÑ3FÈÓVˆ	Ù˜D×2Ò2ØÐØ×.Ò.Ø.2ˆDÔ+Ü×"Ñ"ð#ð ×#Ñ#ô	ô Ðr6   c           	      óÄ   — | j                   j                  D �cg c]  }| j                  ||||«      ‘Œ }}|€d|in||dœ}| j                   j                  |¬«      S c c}w )u·  Clone the config, rewriting headers on the callback's own exporter.

        Both header sets apply only to the exporter ``self._callback_name``
        contributed (``spec.owner``). A request that carries one tenant's Arize
        key must never rewrite the headers of a co-configured Langfuse or
        self-hosted collector exporter, which would leak that key to a
        different backend.

        Dynamic credentials REPLACE the exporter's headers â€” they are the
        tenant's complete credential set. Project headers APPEND instead: the
        preset's static headers carry the backend auth (Phoenix's
        ``Authorization``), which must survive routing to a project.
        rq   )rq   r‰   ©Úupdate)rg   rq   Ú_routed_exporterÚ
model_copy)rv   r‡   rˆ   rŠ   r‰   re   rq   rŸ   s           r2   r�   z TenantTracerCache._routed_configU  sy   € ð, Ÿ™×.Ñ.ö
àð ×!Ñ! $Ð(:¸OÈXÕVð
ˆ	ð 
ð
 )5Ð(<ˆ[˜)Ñ$ÐPYÐkwÑBxð 	ð �|‰|×&Ñ&¨fÐ&Ó5Ð5ùò
s   ™Are   c                 óÎ  — |j                   j                  «       }|j                  | j                  k7  s|t        v r|S |rt        |«      n|j                  }|r+|t        vr#dj                  d„ |t        |«      fD «       «      n|}d|fd|ffD ��	ci c]2  \  }}	|dk(  r||j                  k7  s|dk(  r|�||j                  k7  r||	“Œ4 }
}}	|
s|S |j                  |
¬«      S c c}	}w )Nr@   c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wrb   rC   )r/   Úparts     r2   r3   z5TenantTracerCache._routed_exporter.<locals>.<genexpr>~  s   è ø€ Ò^˜dÒY]”TÑ^ùs   ‚Šr>   rŠ   rž   )rd   Úlowerrc   rh   r   rJ   r>   r   rG   rN   rŠ   r¡   )rv   re   r‡   rˆ   rŠ   rd   ÚbaseÚroutedÚfieldrE   rŸ   s              r2   r    z"TenantTracerCache._routed_exporterr  sü   € ð —i‘i—o‘oÓ'ˆØ�:‰:˜×,Ñ,Ò,°¼Ñ0GØˆKÙBTÔ*Ð+=Ô>ÐZ^×ZfÑZfˆñ  4¬{Ñ#:ð �H‰HÑ^ tÔ-CÀOÓ-TÐ&UÔ^Ô^àð 	ð #,¨VÐ!4°zÀ8Ð6LÐ M÷
á��uØ˜Ò" v°·±Ò'=Ø˜Ò#¨Ð(<ÀÈTÏ]É]ÒAZð �5‰Lð
ˆñ 
ñ "ˆtÐE t§¡¸f Ó'EÐEùó
s   Â7C!rb   )NN)rU   rV   rW   rX   r   Ústrrx   r   r~   r   r   r   rR   r�   r%   r…   r†   r‚   rƒ   r�   r   r    rC   r6   r2   r\   r\   �   sý  „ Ù[ð 3à%ð 3ð ˜T‘zð 3ð ð	 3ð
 
ó 3ðD) °Ñ 5ð )¸$ó )ð2 37ñ	-
àð-
ð 6¸Ñ<ð-
ð ˜s C˜xÑ(¨4Ñ/ð	-
ð
 
ó-
ð^àðð $ C¨ HÑ-ðð !  c Ñ*ð	ð
 ˜‘*ðð ˜D‘jðð 
óð$¨^¸dÑ-Bó ð,Ð2OÐRVÑ2Vð Ð[bÐcfÐhkÐckÑ[ló ð(¨g°c¸3°hÑ.?À$Ñ.Fð È7ÐSVÐX[ÐS[ÑK\ó ð.  $Ø#'ñ6à# C¨ HÑ-ð6ð !  c Ñ*ð6ð ˜‘*ð	6ð
 ˜D‘jð6ð 
ó6ðD  $ñFàðFð $ C¨ HÑ-ðFð !  c Ñ*ð	Fð
 ˜‘*ðFð 
ôFr6   r\   )5rX   rj   Úcollectionsr   Úcollections.abcr   Údataclassesr   Útypesr   Útypingr   r   Úurllib.parser
   Úopentelemetry.sdk.tracer   Úopentelemetry.tracer   Úlitellm._loggingr   Úlitellm.constantsr   Ú&litellm.integrations.otel.model.configr   r   Ú,litellm.integrations.otel.plumbing.providersr   r   r   Ú!litellm.integrations.otel.presetsr   r   r   Úlitellm.types.utilsr   r   rY   r   r!   r#   rp   r©   r$   r%   r&   r'   r5   r=   rJ   rN   rR   r\   rC   r6   r2   ú<module>r¸      sŠ  ðòó Ý #Ý #Ý !Ý "ß #Ý å 2Ý &å +Ý =ß V÷ñ ÷
ñ õ
 >ð H€�Ó Gð
 +€ˆUÓ *ð  #Ð �uÓ "ð !#Ð ˜Ó "à  c¨3 h¡°Ð 4Ñ5€ˆiÓ 5à˜\¨<¸¸t¹ÀSÈ4ÁZÐOÑP€	ˆ9Ó Pá(8¸Ó(<€ˆU�7˜3 ˜8Ñ$Ñ%Ó <ð <Ð �EÓ ;ð
 w¨s°C¨xÑ'8¸4Ñ'?ð 
ÀCÈ$ÁJó 
ðU ð U°Dó UðH '¨#¨s¨(Ñ"3ð H¸ó HðX G¨C°¨HÑ$5ð X¸#ó Xñ �$˜dÔ#÷ +ð  +ó $ð +÷FkFò kFr6   