Skip to content

Commit 95af7b7

Browse files
committed
ref(transport): Added shared sync/async transport superclass and created a sync transport HTTP subclass
Moved shared sync/async logic into a new superclass (HttpTransportCore), and moved sync transport specific code into a new subclass(BaseSyncHttpTransport), from which the current transport implementations inherit Fixes GH-4568
1 parent bd94010 commit 95af7b7

File tree

2 files changed

+92
-63
lines changed

2 files changed

+92
-63
lines changed

sentry_sdk/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
)
2626
from sentry_sdk.serializer import serialize
2727
from sentry_sdk.tracing import trace
28-
from sentry_sdk.transport import BaseHttpTransport, make_transport
28+
from sentry_sdk.transport import HttpTransportCore, make_transport
2929
from sentry_sdk.consts import (
3030
SPANDATA,
3131
DEFAULT_MAX_VALUE_LENGTH,
@@ -403,7 +403,7 @@ def _capture_envelope(envelope: Envelope) -> None:
403403
self.monitor
404404
or self.log_batcher
405405
or has_profiling_enabled(self.options)
406-
or isinstance(self.transport, BaseHttpTransport)
406+
or isinstance(self.transport, HttpTransportCore)
407407
):
408408
# If we have anything on that could spawn a background thread, we
409409
# need to check if it's safe to use them.

sentry_sdk/transport.py

Lines changed: 90 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def _parse_rate_limits(
168168
continue
169169

170170

171-
class BaseHttpTransport(Transport):
171+
class HttpTransportCore(Transport):
172172
"""The base HTTP transport."""
173173

174174
TIMEOUT = 30 # seconds
@@ -292,12 +292,8 @@ def _update_rate_limits(
292292
seconds=retry_after
293293
)
294294

295-
def _send_request(
296-
self: Self,
297-
body: bytes,
298-
headers: Dict[str, str],
299-
endpoint_type: EndpointType = EndpointType.ENVELOPE,
300-
envelope: Optional[Envelope] = None,
295+
def _handle_request_error(
296+
self: Self, envelope: Optional[Envelope], loss_reason: str = "network"
301297
) -> None:
302298
def record_loss(reason: str) -> None:
303299
if envelope is None:
@@ -306,45 +302,45 @@ def record_loss(reason: str) -> None:
306302
for item in envelope.items:
307303
self.record_lost_event(reason, item=item)
308304

305+
self.on_dropped_event(loss_reason)
306+
record_loss("network_error")
307+
308+
def _handle_response(
309+
self: Self,
310+
response: Union[urllib3.BaseHTTPResponse, httpcore.Response],
311+
envelope: Optional[Envelope],
312+
) -> None:
313+
self._update_rate_limits(response)
314+
315+
if response.status == 429:
316+
# if we hit a 429. Something was rate limited but we already
317+
# acted on this in `self._update_rate_limits`. Note that we
318+
# do not want to record event loss here as we will have recorded
319+
# an outcome in relay already.
320+
self.on_dropped_event("status_429")
321+
pass
322+
323+
elif response.status >= 300 or response.status < 200:
324+
logger.error(
325+
"Unexpected status code: %s (body: %s)",
326+
response.status,
327+
getattr(response, "data", getattr(response, "content", None)),
328+
)
329+
self._handle_request_error(
330+
envelope=envelope, loss_reason="status_{}".format(response.status)
331+
)
332+
333+
def _update_headers(
334+
self: Self,
335+
headers: Dict[str, str],
336+
) -> None:
337+
309338
headers.update(
310339
{
311340
"User-Agent": str(self._auth.client),
312341
"X-Sentry-Auth": str(self._auth.to_header()),
313342
}
314343
)
315-
try:
316-
response = self._request(
317-
"POST",
318-
endpoint_type,
319-
body,
320-
headers,
321-
)
322-
except Exception:
323-
self.on_dropped_event("network")
324-
record_loss("network_error")
325-
raise
326-
327-
try:
328-
self._update_rate_limits(response)
329-
330-
if response.status == 429:
331-
# if we hit a 429. Something was rate limited but we already
332-
# acted on this in `self._update_rate_limits`. Note that we
333-
# do not want to record event loss here as we will have recorded
334-
# an outcome in relay already.
335-
self.on_dropped_event("status_429")
336-
pass
337-
338-
elif response.status >= 300 or response.status < 200:
339-
logger.error(
340-
"Unexpected status code: %s (body: %s)",
341-
response.status,
342-
getattr(response, "data", getattr(response, "content", None)),
343-
)
344-
self.on_dropped_event("status_{}".format(response.status))
345-
record_loss("network_error")
346-
finally:
347-
response.close()
348344

349345
def on_dropped_event(self: Self, _reason: str) -> None:
350346
return None
@@ -381,11 +377,6 @@ def _fetch_pending_client_report(
381377
type="client_report",
382378
)
383379

384-
def _flush_client_reports(self: Self, force: bool = False) -> None:
385-
client_report = self._fetch_pending_client_report(force=force, interval=60)
386-
if client_report is not None:
387-
self.capture_envelope(Envelope(items=[client_report]))
388-
389380
def _check_disabled(self: Self, category: EventDataCategory) -> bool:
390381
def _disabled(bucket: Optional[EventDataCategory]) -> bool:
391382
ts = self._disabled_until.get(bucket)
@@ -404,9 +395,9 @@ def _is_worker_full(self: Self) -> bool:
404395
def is_healthy(self: Self) -> bool:
405396
return not (self._is_worker_full() or self._is_rate_limited())
406397

407-
def _send_envelope(self: Self, envelope: Envelope) -> None:
408-
409-
# remove all items from the envelope which are over quota
398+
def _prepare_envelope(
399+
self: Self, envelope: Envelope
400+
) -> Optional[Tuple[Envelope, io.BytesIO, Dict[str, str]]]:
410401
new_items = []
411402
for item in envelope.items:
412403
if self._check_disabled(item.data_category):
@@ -448,13 +439,7 @@ def _send_envelope(self: Self, envelope: Envelope) -> None:
448439
if content_encoding:
449440
headers["Content-Encoding"] = content_encoding
450441

451-
self._send_request(
452-
body.getvalue(),
453-
headers=headers,
454-
endpoint_type=EndpointType.ENVELOPE,
455-
envelope=envelope,
456-
)
457-
return None
442+
return envelope, body, headers
458443

459444
def _serialize_envelope(
460445
self: Self, envelope: Envelope
@@ -512,6 +497,54 @@ def _request(
512497
) -> Union[urllib3.BaseHTTPResponse, httpcore.Response]:
513498
raise NotImplementedError()
514499

500+
def kill(self: Self) -> None:
501+
logger.debug("Killing HTTP transport")
502+
self._worker.kill()
503+
504+
505+
class BaseSyncHttpTransport(HttpTransportCore):
506+
507+
def _send_envelope(self: Self, envelope: Envelope) -> None:
508+
_prepared_envelope = self._prepare_envelope(envelope)
509+
if _prepared_envelope is None: # TODO: check this behaviour in detail
510+
return None
511+
envelope, body, headers = _prepared_envelope
512+
self._send_request(
513+
body.getvalue(),
514+
headers=headers,
515+
endpoint_type=EndpointType.ENVELOPE,
516+
envelope=envelope,
517+
)
518+
return None
519+
520+
def _send_request(
521+
self: Self,
522+
body: bytes,
523+
headers: Dict[str, str],
524+
endpoint_type: EndpointType,
525+
envelope: Optional[Envelope],
526+
) -> None:
527+
self._update_headers(headers)
528+
try:
529+
response = self._request(
530+
"POST",
531+
endpoint_type,
532+
body,
533+
headers,
534+
)
535+
except Exception:
536+
self._handle_request_error(envelope=envelope, loss_reason="network")
537+
raise
538+
try:
539+
self._handle_response(response=response, envelope=envelope)
540+
finally:
541+
response.close()
542+
543+
def _flush_client_reports(self: Self, force: bool = False) -> None:
544+
client_report = self._fetch_pending_client_report(force=force, interval=60)
545+
if client_report is not None:
546+
self.capture_envelope(Envelope(items=[client_report]))
547+
515548
def capture_envelope(self: Self, envelope: Envelope) -> None:
516549
def send_envelope_wrapper() -> None:
517550
with capture_internal_exceptions():
@@ -534,12 +567,8 @@ def flush(
534567
self._worker.submit(lambda: self._flush_client_reports(force=True))
535568
self._worker.flush(timeout, callback)
536569

537-
def kill(self: Self) -> None:
538-
logger.debug("Killing HTTP transport")
539-
self._worker.kill()
540-
541570

542-
class HttpTransport(BaseHttpTransport):
571+
class HttpTransport(BaseSyncHttpTransport):
543572
if TYPE_CHECKING:
544573
_pool: Union[PoolManager, ProxyManager]
545574

@@ -656,7 +685,7 @@ def __init__(self: Self, options: Dict[str, Any]) -> None:
656685

657686
else:
658687

659-
class Http2Transport(BaseHttpTransport): # type: ignore
688+
class Http2Transport(BaseSyncHttpTransport): # type: ignore
660689
"""The HTTP2 transport based on httpcore."""
661690

662691
TIMEOUT = 15

0 commit comments

Comments
 (0)