Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 117 additions & 27 deletions sentry_sdk/transport.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -1081,6 +1083,89 @@
return httpcore.ConnectionPool(**opts)


class _EnvelopePrinterTransport(Transport):
"""Prints envelope contents to the SDK debug logger, optionally wrapping another transport."""

def __init__(self, transport: "Optional[Transport]" = None) -> None:
if transport is not None:
Transport.__init__(self, options=transport.options)
self.parsed_dsn = transport.parsed_dsn
else:
Transport.__init__(self)
self._inner = transport

@property # type: ignore[misc]
def __class__(self) -> type:
if self._inner is not None:

Check failure on line 1099 in sentry_sdk/transport.py

View check run for this annotation

@sentry/warden / warden: code-review

Overriding __class__ as a property breaks isinstance and type identity

The `__class__` property dynamically returns `self._inner.__class__`, which causes `isinstance(printer, _EnvelopePrinterTransport)` to return False and `type(printer)` to report the inner transport's class. This is a significant side effect that can confuse Sentry's internal type checks (e.g., `isinstance(transport, HttpTransport)` checks elsewhere in the SDK), break debugging/logging, and cause subtle bugs in any code that relies on type identity of the transport. It also conflicts with backwards compatibility expectations of the Transport interface.
return self._inner.__class__
return _EnvelopePrinterTransport

def capture_envelope(self, envelope: "Envelope") -> None:
try:
logger.debug("--- Sentry Envelope ---")
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
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: <binary %d bytes>",
len(item.get_bytes()),
)
Comment thread
sentry[bot] marked this conversation as resolved.
logger.debug("--- End Envelope ---")
Comment thread
sentry-warden[bot] marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
except Exception:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent exception swallowing hides debug tool failures

Low Severity

The bare except Exception: pass in capture_envelope silently swallows all errors during envelope printing without any feedback. Since this is specifically a debugging tool activated by SENTRY_PRINT_ENVELOPES=1, silently failing defeats the tool's purpose — a developer who enables it and sees no output would have no way to know why. The SDK's own capture_internal_exceptions pattern logs exceptions rather than discarding them entirely.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8d69e7b. Configure here.


if self._inner is not None:
self._inner.capture_envelope(envelope)

def flush(
self,
timeout: float,
callback: "Optional[Any]" = None,
) -> "Any":
if self._inner is not None:
return self._inner.flush(timeout, callback)

def kill(self) -> "Any":
if self._inner is not None:
return self._inner.kill()

def record_lost_event(
self,
reason: str,
data_category: "Optional[EventDataCategory]" = None,
item: "Optional[Item]" = None,
*,
quantity: int = 1,
) -> None:
if self._inner is not None:
self._inner.record_lost_event(
reason, data_category, item, quantity=quantity
)

def is_healthy(self) -> bool:
if self._inner is not None:
return self._inner.is_healthy()
return True

def __getattr__(self, name: str) -> "Any":
if self._inner is not None:
return getattr(self._inner, name)
raise AttributeError(name)


class _FunctionTransport(Transport):
"""
DEPRECATED: Users wishing to provide a custom transport should subclass
Expand Down Expand Up @@ -1147,8 +1232,10 @@
"You tried to use AsyncHttpTransport but don't have httpcore[asyncio] installed. Falling back to sync transport."
)

transport = None # type: Optional[Transport]
Comment thread
ericapisani marked this conversation as resolved.
Outdated

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):
Expand All @@ -1158,11 +1245,14 @@
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
Comment thread
ericapisani marked this conversation as resolved.
if options["dsn"]:
return transport_cls(options)
if transport is None and options["dsn"]:
transport = transport_cls(options)

if os.environ.get("SENTRY_PRINT_ENVELOPES", "").lower() in ("1", "true", "yes"):
transport = _EnvelopePrinterTransport(transport)
Comment thread
cursor[bot] marked this conversation as resolved.

return None
return transport
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
18 changes: 9 additions & 9 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best to remove the changes in test_transport.py since we're not adding any tests. Although it's only import ordering so this is a nitpick.

)
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

Expand Down
Loading