1919import requests
2020import httpx
2121from typing_extensions import TypeGuard
22+ import time
2223
2324from confidence import __version__
2425from confidence .errors import (
3031)
3132from .flag_types import FlagResolutionDetails , Reason , ErrorCode
3233from .names import FlagName , VariantName
34+ from .telemetry import Telemetry , ProtoTraceId , ProtoStatus
3335
3436EU_RESOLVE_API_ENDPOINT = "https://resolver.eu.confidence.dev"
3537US_RESOLVE_API_ENDPOINT = "https://resolver.us.confidence.dev"
@@ -111,6 +113,16 @@ def __init__(
111113 self .async_client = async_client
112114 self ._setup_logger (logger )
113115 self ._custom_resolve_base_url = custom_resolve_base_url
116+ self ._telemetry = Telemetry (__version__ )
117+
118+ def _get_resolve_headers (self ) -> Dict [str , str ]:
119+ telemetry_header = self ._telemetry .get_monitoring_header ()
120+ headers = {
121+ "Content-Type" : "application/json" ,
122+ "Accept" : "application/json" ,
123+ "X-CONFIDENCE-TELEMETRY" : telemetry_header
124+ }
125+ return headers
114126
115127 def resolve_boolean_details (
116128 self , flag_key : str , default_value : bool
@@ -367,7 +379,6 @@ def _send_event_internal(self, event_name: str, data: Dict[str, FieldType]) -> N
367379 )
368380 if response .status_code == 200 :
369381 json = response .json ()
370-
371382 json_errors = json .get ("errors" )
372383 if json_errors :
373384 self .logger .warning ("events emitted with errors:" )
@@ -407,37 +418,55 @@ def _handle_resolve_response(
407418 def _resolve (
408419 self , flag_name : FlagName , context : Dict [str , FieldType ]
409420 ) -> ResolveResult :
410- request_body = {
411- "clientSecret" : self ._client_secret ,
412- "evaluationContext" : context ,
413- "apply" : self ._apply_on_resolve ,
414- "flags" : [str (flag_name )],
415- "sdk" : {"id" : "SDK_ID_PYTHON_CONFIDENCE" , "version" : __version__ },
416- }
417- base_url = self ._api_endpoint
418- if self ._custom_resolve_base_url is not None :
419- base_url = self ._custom_resolve_base_url
420-
421- resolve_url = f"{ base_url } /v1/flags:resolve"
422- timeout_sec = None if self ._timeout_ms is None else self ._timeout_ms / 1000.0
421+ start_time = time .perf_counter ()
423422 try :
424- response = requests .post (
425- resolve_url , json = request_body , timeout = timeout_sec
426- )
427- return self ._handle_resolve_response (response , flag_name )
428- except requests .exceptions .Timeout :
429- self .logger .warning (
430- f"Request timed out after { timeout_sec } s"
431- f" when resolving flag { flag_name } "
432- )
433- raise TimeoutError ()
434- except requests .exceptions .RequestException as e :
435- self .logger .warning (f"Error resolving flag { flag_name } : { str (e )} " )
436- raise GeneralError (str (e ))
423+ request_body = {
424+ "clientSecret" : self ._client_secret ,
425+ "evaluationContext" : context ,
426+ "apply" : self ._apply_on_resolve ,
427+ "flags" : [str (flag_name )],
428+ "sdk" : {"id" : "SDK_ID_PYTHON_CONFIDENCE" , "version" : __version__ },
429+ }
430+ base_url = self ._api_endpoint
431+ if self ._custom_resolve_base_url is not None :
432+ base_url = self ._custom_resolve_base_url
433+
434+ resolve_url = f"{ base_url } /v1/flags:resolve"
435+ timeout_sec = None if self ._timeout_ms is None else self ._timeout_ms / 1000.0
436+
437+ try :
438+ response = requests .post (
439+ resolve_url ,
440+ json = request_body ,
441+ headers = self ._get_resolve_headers (),
442+ timeout = timeout_sec
443+ )
444+
445+ result = self ._handle_resolve_response (response , flag_name )
446+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
447+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_SUCCESS )
448+ return result
449+ except requests .exceptions .Timeout :
450+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
451+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_TIMEOUT )
452+ self .logger .warning (
453+ f"Request timed out after { timeout_sec } s"
454+ f" when resolving flag { flag_name } "
455+ )
456+ raise TimeoutError ()
457+ except requests .exceptions .RequestException as e :
458+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
459+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_ERROR )
460+ self .logger .warning (f"Error resolving flag { flag_name } : { str (e )} " )
461+ raise GeneralError (str (e ))
462+ except Exception as e :
463+ # Just re-raise any other exceptions without adding another trace
464+ raise e
437465
438466 async def _resolve_async (
439467 self , flag_name : FlagName , context : Dict [str , FieldType ]
440468 ) -> ResolveResult :
469+ start_time = time .perf_counter ()
441470 request_body = {
442471 "clientSecret" : self ._client_secret ,
443472 "evaluationContext" : context ,
@@ -453,16 +482,26 @@ async def _resolve_async(
453482 timeout_sec = None if self ._timeout_ms is None else self ._timeout_ms / 1000.0
454483 try :
455484 response = await self .async_client .post (
456- resolve_url , json = request_body , timeout = timeout_sec
485+ resolve_url ,
486+ json = request_body ,
487+ headers = self ._get_resolve_headers (),
488+ timeout = timeout_sec
457489 )
458- return self ._handle_resolve_response (response , flag_name )
490+ result = self ._handle_resolve_response (response , flag_name )
491+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
492+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_SUCCESS )
493+ return result
459494 except httpx .TimeoutException :
495+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
496+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_TIMEOUT )
460497 self .logger .warning (
461498 f"Request timed out after { timeout_sec } s"
462499 f" when resolving flag { flag_name } "
463500 )
464501 raise TimeoutError ()
465502 except httpx .HTTPError as e :
503+ duration_ms = int ((time .perf_counter () - start_time ) * 1000 )
504+ self ._telemetry .add_trace (ProtoTraceId .PROTO_TRACE_ID_RESOLVE_LATENCY , duration_ms , ProtoStatus .PROTO_STATUS_ERROR )
466505 self .logger .warning (f"Error resolving flag { flag_name } : { str (e )} " )
467506 raise GeneralError (str (e ))
468507
0 commit comments