feat(transport): Add EnvelopePrinterTransport for debug logging#6181
feat(transport): Add EnvelopePrinterTransport for debug logging#6181ericapisani wants to merge 11 commits intomasterfrom
Conversation
Add a decorator transport that logs envelope contents via the SDK debug logger before forwarding to the inner transport. Activated by setting SENTRY_PRINT_ENVELOPES=1|true|yes. Includes tests for delegation, logging behavior, make_transport integration, and strict env var parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Results 📊✅ 13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 9.78s 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ❌ Patch coverage is 37.10%. Project has 15089 uncovered lines. Files with missing lines (1)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 31.35% 31.35% —%
==========================================
Files 190 190 —
Lines 21930 21980 +50
Branches 7382 7402 +20
==========================================
+ Hits 6876 6891 +15
- Misses 15054 15089 +35
- Partials 581 583 +2Generated by Codecov Action |
|
bugbot run |
|
@sentry review |
…sport coverage - Override __class__ to delegate to inner transport so isinstance checks (e.g. AsyncHttpTransport) remain transparent - Add __getattr__ fallback for attributes not explicitly defined - Return values from flush() and kill() so async tasks propagate - Wrap transport in all make_transport paths (pre-instantiated, callable) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
bugbot run |
|
@sentry review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f050b85. Configure here.
…ry/sentry-python into envelope-printer-transport-zcgvd
There was a problem hiding this comment.
The snippet below should probably work without crashing, since that's the canonical way to provide an alternative transport.
Maybe we create a transport which only prints to the terminal without wrapping another transport?
import sentry_sdk
from sentry_sdk.transport import _EnvelopePrinterTransport
sentry_sdk.init(
dsn="...",
transport=_EnvelopePrinterTransport,
)|
@alexander-alderman-webb Given this transport is for debugging purposes on our end, I opted for the wrapping approach so that we don't need to change how we've configured the SDK, which was intended to include specific transports we may be leveraging for our use case. Is there something in particular that you're concerned about with this approach? |
|
My main concern is that we need to set I agree that wrapping would be the nicest approach but I don't think our current abstractions were designed for this kind of wrapping 😞. |
| ) | ||
| logger.debug("--- End Envelope ---") | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 8d69e7b. Configure here.
| " Payload: <binary %d bytes>", | ||
| len(item.get_bytes()), | ||
| ) | ||
| logger.debug("--- End Envelope ---") |
There was a problem hiding this comment.
Envelope printing uses debug level, invisible without debug=True
Medium Severity
All logging in _EnvelopePrinterTransport.capture_envelope uses logger.debug(), but the SDK's _DebugFilter (in debug.py) suppresses all log messages when debug=True is not set in the SDK options. This means SENTRY_PRINT_ENVELOPES=1 alone won't produce any output — users must also configure debug=True, which contradicts the PR description stating the feature is "Enabled via SENTRY_PRINT_ENVELOPES=1." The reviewer explicitly discussed this and suggested using logger.info instead, but the change hasn't been applied to any of the logging calls.
Reviewed by Cursor Bugbot for commit 1471ea2. Configure here.
| def __init__(self, transport: "Transport") -> None: | ||
| Transport.__init__(self, options=transport.options) | ||
| self._inner = transport | ||
| self.parsed_dsn = transport.parsed_dsn |
There was a problem hiding this comment.
| def __init__(self, transport: "Transport") -> None: | |
| Transport.__init__(self, options=transport.options) | |
| self._inner = transport | |
| self.parsed_dsn = transport.parsed_dsn | |
| def __init__(self, transport: "Transport") -> None: | |
| Transport.__init__(self, options=transport.options) | |
| self._inner = transport | |
| self.parsed_dsn = transport.parsed_dsn | |
| self.envelope_logger = logging.getLogger("sentry_sdk.envelopes") | |
| self.envelope_logger.setLevel(logging.DEBUG) |
| if os.environ.get("SENTRY_PRINT_ENVELOPES", "").lower() in ("1", "true", "yes"): | ||
| transport = _EnvelopePrinterTransport(transport) | ||
|
|
||
| return None | ||
| return transport |
There was a problem hiding this comment.
Bug: When SENTRY_PRINT_ENVELOPES=1 is set without a DSN, make_transport returns a truthy object instead of None, causing unintended side effects like spawning a background thread.
Severity: MEDIUM
Suggested Fix
In make_transport, the _EnvelopePrinterTransport should only wrap the transport object if it is not None. This ensures that when no DSN is configured and no other transport is available, make_transport correctly returns None, preventing the creation of unnecessary resources.
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#L1255-L1258
Potential issue: When the `SENTRY_PRINT_ENVELOPES=1` environment variable is set without
a DSN configured, the `make_transport` function incorrectly returns a truthy
`_EnvelopePrinterTransport(None)` object instead of `None`. This causes the `Client` to
behave as if a valid transport exists, leading to several unintended side effects. A
backpressure `Monitor` is created, which in turn spawns a background daemon thread that
runs unnecessarily. It also triggers a superfluous call to
`check_uwsgi_thread_support()`. Furthermore, `capture_event` will return a non-`None`
event ID, giving the false impression that an event was sent when it was actually
dropped.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5205418. Configure here.
| transport = transport_cls(options) | ||
|
|
||
| if os.environ.get("SENTRY_PRINT_ENVELOPES", "").lower() in ("1", "true", "yes"): | ||
| transport = _EnvelopePrinterTransport(transport) |
There was a problem hiding this comment.
Wrapping None transport changes make_transport return behavior
Medium Severity
When SENTRY_PRINT_ENVELOPES is set but no DSN is configured, make_transport now returns a non-None _EnvelopePrinterTransport(None) instead of None. Code in client.py that checks if self.transport: (e.g., to set up Monitor, call record_lost_event, check parsed_dsn) now takes branches that were previously skipped without a DSN. The wrapping of transport here likely needs a guard to only wrap when transport is not None.
Reviewed by Cursor Bugbot for commit 5205418. Configure here.


Adds
EnvelopePrinterTransport, a decorator transport that wraps the real transport and pretty-prints each envelope's headers and item payloads to the SDK debug logger before forwarding the envelope.Enabled via
SENTRY_PRINT_ENVELOPES=1(also acceptstrue/yes). When unset or falsy, no wrapping occurs and there's no runtime cost. Useful for local debugging without having to run a local Sentry instance or intercept network traffic.Fixes PY-2398
Fixes #6183