diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index 1bdc7c6a7d..cf19f6d16b 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -1,14 +1,15 @@ -from abc import ABC, abstractmethod import asyncio +import gzip import io +import json import os -import gzip import socket import ssl import time import warnings -from datetime import datetime, timedelta, timezone +from abc import ABC, abstractmethod from collections import defaultdict +from datetime import datetime, timedelta, timezone from urllib.request import getproxies try: @@ -35,36 +36,37 @@ except ImportError: ASYNC_TRANSPORT_AVAILABLE = False -import urllib3 +from typing import TYPE_CHECKING, Dict, List, cast + import certifi +import urllib3 import sentry_sdk from sentry_sdk.consts import EndpointType +from sentry_sdk.envelope import Envelope, Item, PayloadRef from sentry_sdk.utils import ( Dsn, - logger, capture_internal_exceptions, + logger, mark_sentry_task_internal, ) -from sentry_sdk.worker import BackgroundWorker, Worker, AsyncWorker -from sentry_sdk.envelope import Envelope, Item, PayloadRef - -from typing import TYPE_CHECKING, cast, List, Dict +from sentry_sdk.worker import AsyncWorker, BackgroundWorker, Worker if TYPE_CHECKING: - from typing import Any - from typing import Callable - from typing import DefaultDict - from typing import Iterable - from typing import Mapping - from typing import Optional - from typing import Self - from typing import Tuple - from typing import Type - from typing import Union - - from urllib3.poolmanager import PoolManager - from urllib3.poolmanager import ProxyManager + from typing import ( + Any, + Callable, + DefaultDict, + Iterable, + Mapping, + Optional, + Self, + Tuple, + Type, + Union, + ) + + from urllib3.poolmanager import PoolManager, ProxyManager from sentry_sdk._types import Event, EventDataCategory @@ -1081,6 +1083,74 @@ def _make_pool( return httpcore.ConnectionPool(**opts) +class _EnvelopePrinterTransport(Transport): + """Wraps another transport, printing envelope contents to the SDK debug logger before sending.""" + + def __init__(self, transport: "Transport") -> None: + Transport.__init__(self, options=transport.options) + self._inner = transport + self.parsed_dsn = transport.parsed_dsn + + @property # type: ignore[misc] + def __class__(self) -> type: + return self._inner.__class__ + + def capture_envelope(self, envelope: "Envelope") -> None: + try: + logger.debug("--- Sentry Envelope ---") + logger.debug( + "Headers: %s", json.dumps(envelope.headers, indent=2, default=str) + ) + for item in envelope.items: + logger.debug(" Item type: %s", item.type) + logger.debug( + " Item headers: %s", + json.dumps(item.headers, indent=2, default=str), + ) + try: + payload = json.loads(item.get_bytes()) + logger.debug( + " Payload:\n%s", + json.dumps(payload, indent=2, default=str), + ) + except (ValueError, TypeError): + logger.debug( + " Payload: ", + len(item.get_bytes()), + ) + logger.debug("--- End Envelope ---") + except Exception: + pass + + self._inner.capture_envelope(envelope) + + def flush( + self, + timeout: float, + callback: "Optional[Any]" = None, + ) -> "Any": + return self._inner.flush(timeout, callback) + + def kill(self) -> "Any": + return self._inner.kill() + + def record_lost_event( + self, + reason: str, + data_category: "Optional[EventDataCategory]" = None, + item: "Optional[Item]" = None, + *, + quantity: int = 1, + ) -> None: + self._inner.record_lost_event(reason, data_category, item, quantity=quantity) + + def is_healthy(self) -> bool: + return self._inner.is_healthy() + + def __getattr__(self, name: str) -> "Any": + return getattr(self._inner, name) + + class _FunctionTransport(Transport): """ DEPRECATED: Users wishing to provide a custom transport should subclass @@ -1147,8 +1217,10 @@ def make_transport(options: "Dict[str, Any]") -> "Optional[Transport]": "You tried to use AsyncHttpTransport but don't have httpcore[asyncio] installed. Falling back to sync transport." ) + transport = None # type: Optional[Transport] + if isinstance(ref_transport, Transport): - return ref_transport + transport = ref_transport elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport): transport_cls = ref_transport elif callable(ref_transport): @@ -1158,11 +1230,16 @@ def make_transport(options: "Dict[str, Any]") -> "Optional[Transport]": DeprecationWarning, stacklevel=2, ) - return _FunctionTransport(ref_transport) + transport = _FunctionTransport(ref_transport) # if a transport class is given only instantiate it if the dsn is not # empty or None - if options["dsn"]: - return transport_cls(options) + if transport is None and options["dsn"]: + transport = transport_cls(options) + + if transport is not None and os.environ.get( + "SENTRY_PRINT_ENVELOPES", "" + ).lower() in ("1", "true", "yes"): + transport = _EnvelopePrinterTransport(transport) - return None + return transport diff --git a/tests/test_transport.py b/tests/test_transport.py index a121d3f1be..ae675c8516 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -1,14 +1,15 @@ +import asyncio import logging -import pickle import os +import pickle import socket import sys -import asyncio from collections import defaultdict from datetime import datetime, timedelta, timezone from unittest import mock import pytest + from tests.conftest import CapturingServer try: @@ -30,23 +31,22 @@ import sentry_sdk from sentry_sdk import ( Client, + Hub, add_breadcrumb, capture_message, - isolation_scope, get_isolation_scope, - Hub, + isolation_scope, ) from sentry_sdk._compat import PY37, PY38 -from sentry_sdk.envelope import Envelope, Item, parse_json, PayloadRef +from sentry_sdk.envelope import Envelope, Item, PayloadRef, parse_json +from sentry_sdk.integrations.asyncio import AsyncioIntegration +from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger from sentry_sdk.transport import ( KEEP_ALIVE_SOCKET_OPTIONS, - _parse_rate_limits, AsyncHttpTransport, HttpTransport, + _parse_rate_limits, ) -from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger -from sentry_sdk.integrations.asyncio import AsyncioIntegration - server = None