|
26 | 26 | ) |
27 | 27 | from .base import ExportResult, SpanExportAdapter |
28 | 28 |
|
| 29 | + |
| 30 | +class NonRetryableError(Exception): |
| 31 | + """Error that should not be retried (e.g., 4xx client errors).""" |
| 32 | + |
| 33 | + pass |
| 34 | + |
| 35 | + |
29 | 36 | if TYPE_CHECKING: |
30 | 37 | from betterproto.lib.google.protobuf import Struct |
31 | 38 |
|
@@ -162,11 +169,12 @@ async def export_spans(self, spans: list[CleanSpanData]) -> ExportResult: |
162 | 169 | async def do_export() -> ExportResult: |
163 | 170 | return await self._do_export(spans) |
164 | 171 |
|
165 | | - # Execute with retry |
| 172 | + # Execute with retry (NonRetryableError bypasses retry) |
166 | 173 | result = await retry_async( |
167 | 174 | do_export, |
168 | 175 | config=self._retry_config, |
169 | | - retryable_exceptions=(Exception,), # Retry all exceptions |
| 176 | + retryable_exceptions=(Exception,), |
| 177 | + non_retryable_exceptions=(NonRetryableError,), |
170 | 178 | operation_name="span_export", |
171 | 179 | ) |
172 | 180 |
|
@@ -231,9 +239,9 @@ async def _do_export(self, spans: list[CleanSpanData]) -> ExportResult: |
231 | 239 | error_text = await http_response.text() |
232 | 240 | raise Exception(f"Server error (status {http_response.status}): {error_text}") |
233 | 241 | elif http_response.status != 200: |
234 | | - # Client errors (4xx) are not retryable |
| 242 | + # Client errors (4xx) are not retryable - fail immediately |
235 | 243 | error_text = await http_response.text() |
236 | | - raise Exception(f"API request failed (status {http_response.status}): {error_text}") |
| 244 | + raise NonRetryableError(f"Client error (status {http_response.status}): {error_text}") |
237 | 245 |
|
238 | 246 | response_bytes = await http_response.read() |
239 | 247 | response = ExportSpansResponse().parse(response_bytes) |
|
0 commit comments