@@ -86,9 +86,9 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
8686 logger .info ("No spans with tenant/agent identity found; nothing exported." )
8787 return SpanExportResult .SUCCESS
8888
89- # Debug: Log number of groups and total span count
89+ # Log number of groups and total span count
9090 total_spans = sum (len (activities ) for activities in groups .values ())
91- logger .info (
91+ logger .debug (
9292 f"Found { len (groups )} identity groups with { total_spans } total spans to export"
9393 )
9494
@@ -105,8 +105,8 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
105105
106106 url = build_export_url (endpoint , agent_id , tenant_id , self ._use_s2s_endpoint )
107107
108- # Debug: Log endpoint being used
109- logger .info (
108+ # Log endpoint details at DEBUG to avoid leaking IDs in production logs
109+ logger .debug (
110110 f"Exporting { len (activities )} spans to endpoint: { url } "
111111 f"(tenant: { tenant_id } , agent: { agent_id } )"
112112 )
@@ -115,15 +115,19 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
115115 try :
116116 token = self ._token_resolver (agent_id , tenant_id )
117117 if token :
118+ # Warn if sending bearer token over non-HTTPS connection
119+ if not url .lower ().startswith ("https://" ):
120+ logger .warning (
121+ "Bearer token is being sent over a non-HTTPS connection. "
122+ "This may expose credentials in transit."
123+ )
118124 headers ["authorization" ] = f"Bearer { token } "
119- logger .info ( f "Token resolved successfully for agent { agent_id } " )
125+ logger .debug ( "Token resolved successfully. " )
120126 else :
121- logger .info ( f "No token returned for agent { agent_id } " )
127+ logger .debug ( "No token returned by resolver. " )
122128 except Exception as e :
123129 # If token resolution fails, treat as failure for this group
124- logger .error (
125- f"Token resolution failed for agent { agent_id } , tenant { tenant_id } : { e } "
126- )
130+ logger .error (f"Token resolution failed: { type (e ).__name__ } " )
127131 any_failure = True
128132 continue
129133
@@ -162,6 +166,21 @@ def _truncate_text(text: str, max_length: int) -> str:
162166 return text [:max_length ] + "..."
163167 return text
164168
169+ @staticmethod
170+ def _parse_retry_after (resp : requests .Response ) -> float | None :
171+ """Parse the Retry-After header from a response.
172+
173+ Returns:
174+ The number of seconds to wait, or None if the header is absent or invalid.
175+ """
176+ retry_after = resp .headers .get ("Retry-After" )
177+ if retry_after is None :
178+ return None
179+ try :
180+ return float (retry_after )
181+ except (ValueError , TypeError ):
182+ return None
183+
165184 def _post_with_retries (self , url : str , body : str , headers : dict [str , str ]) -> bool :
166185 for attempt in range (DEFAULT_MAX_RETRIES + 1 ):
167186 try :
@@ -181,43 +200,46 @@ def _post_with_retries(self, url: str, body: str, headers: dict[str, str]) -> bo
181200
182201 # 2xx => success
183202 if 200 <= resp .status_code < 300 :
184- logger .info (
203+ logger .debug (
185204 f"HTTP { resp .status_code } success on attempt { attempt + 1 } . "
186- f"Correlation ID: { correlation_id } . "
187- f"Response: { self ._truncate_text (resp .text , 200 )} "
205+ f"Correlation ID: { correlation_id } ."
188206 )
189207 return True
190208
191- # Log non-success responses
192- response_text = self ._truncate_text (resp .text , 500 )
193-
194209 # Retry transient
195210 if resp .status_code in (408 , 429 ) or 500 <= resp .status_code < 600 :
211+ # Respect Retry-After header for 429 responses
212+ retry_after = self ._parse_retry_after (resp )
196213 if attempt < DEFAULT_MAX_RETRIES :
197- time .sleep (0.2 * (attempt + 1 ))
214+ if retry_after is not None :
215+ time .sleep (min (retry_after , 60.0 ))
216+ else :
217+ # Exponential backoff with base 0.5s
218+ time .sleep (0.5 * (2 ** attempt ))
198219 continue
199220 # Final attempt failed
200221 logger .error (
201- f"HTTP { resp .status_code } final failure after { DEFAULT_MAX_RETRIES + 1 } attempts. "
202- f"Correlation ID: { correlation_id } . "
203- f"Response : { response_text } "
222+ f"HTTP { resp .status_code } final failure after "
223+ f"{ DEFAULT_MAX_RETRIES + 1 } attempts . "
224+ f"Correlation ID : { correlation_id } . "
204225 )
205226 else :
206227 # Non-retryable error
207228 logger .error (
208229 f"HTTP { resp .status_code } non-retryable error. "
209- f"Correlation ID: { correlation_id } . "
210- f"Response: { response_text } "
230+ f"Correlation ID: { correlation_id } ."
211231 )
212232 return False
213233
214234 except requests .RequestException as e :
215235 if attempt < DEFAULT_MAX_RETRIES :
216- time .sleep (0.2 * (attempt + 1 ))
236+ # Exponential backoff with base 0.5s
237+ time .sleep (0.5 * (2 ** attempt ))
217238 continue
218239 # Final attempt failed
219240 logger .error (
220- f"Request failed after { DEFAULT_MAX_RETRIES + 1 } attempts with exception: { e } "
241+ f"Request failed after { DEFAULT_MAX_RETRIES + 1 } attempts: "
242+ f"{ type (e ).__name__ } "
221243 )
222244 return False
223245 return False
0 commit comments