Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
131 changes: 104 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,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
Comment on lines +1089 to +1092
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: A KeyError can occur in _EnvelopePrinterTransport when wrapping a custom transport that has a non-empty options dictionary without a "dsn" key.
Severity: LOW

Suggested Fix

In Transport.__init__, modify the condition to safely check for the existence of the "dsn" key before accessing it, for example, by using options.get("dsn") or "dsn" in options.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: sentry_sdk/transport.py#L1089-L1092

Potential issue: The `_EnvelopePrinterTransport` wraps a given transport and calls the
base `Transport.__init__` with the inner transport's `options`. The base `__init__`
method then executes `if options and options["dsn"]`. If a user provides a custom
transport with a non-empty `options` dictionary that lacks a `"dsn"` key, this check
will raise a `KeyError`. This bug is triggered when the `SENTRY_PRINT_ENVELOPES=1`
environment variable is set and a pre-instantiated custom transport is used.


@property # type: ignore[misc]
def __class__(self) -> type:
return self._inner.__class__
Comment thread
ericapisani marked this conversation as resolved.

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: <binary %d bytes>",
len(item.get_bytes()),
)
logger.debug("--- End Envelope ---")
Comment thread
sentry-warden[bot] marked this conversation as resolved.
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()
Comment thread
ericapisani marked this conversation as resolved.

def __getattr__(self, name: str) -> "Any":
return getattr(self._inner, name)


class _FunctionTransport(Transport):
"""
DEPRECATED: Users wishing to provide a custom transport should subclass
Expand Down Expand Up @@ -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):
Expand All @@ -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
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 transport is not None and os.environ.get(
"SENTRY_PRINT_ENVELOPES", ""
).lower() in ("1", "true", "yes"):
transport = _EnvelopePrinterTransport(transport)

return None
return transport
Loading
Loading