Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 8 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from importlib import import_module
from typing import TYPE_CHECKING, List, Dict, cast, overload
import warnings

import re
from sentry_sdk._compat import check_uwsgi_thread_support
from sentry_sdk._metrics_batcher import MetricsBatcher
from sentry_sdk._span_batcher import SpanBatcher
Expand Down Expand Up @@ -688,6 +688,7 @@
):
new_event = None
spans_before = len(cast(List[Dict[str, object]], event.get("spans", [])))
self._clean_span_descriptions(event)
with capture_internal_exceptions():
new_event = before_send_transaction(event, hint or {})
if new_event is None:
Expand All @@ -712,6 +713,12 @@

return event

def _clean_span_descriptions(self, event: "Event") -> None:
for s in event.get("spans", []):
if "description" in s:
cleaned_description = re.sub(r"\s+", " ", s["description"]).strip()

Check failure on line 719 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: find-bugs

TypeError crash when span description is None

The `_clean_span_descriptions` method checks `if "description" in s:` which only verifies the key exists, not that its value is non-None. When `s["description"]` is `None` (which is valid per the span serialization at `tracing.py:702`), calling `re.sub(r"\s+", " ", s["description"])` raises `TypeError: expected string or bytes-like object, got 'NoneType'`. Since this code runs outside `capture_internal_exceptions()`, the error will propagate and could crash transaction processing.
s["description"] = cleaned_description

Check warning on line 720 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: code-review

Runtime error when span description is None

The `_clean_span_descriptions` method calls `re.sub()` on `s["description"]` after checking `if "description" in s`, but `description` can be `None` (spans are created via `to_json()` which always includes `"description": self.description`, even when the description is None). This will raise `TypeError: expected string or bytes-like object, got 'NoneType'`. Since the call to `_clean_span_descriptions` is outside the `capture_internal_exceptions()` context manager, this exception could cause event processing to fail.
Comment thread
sentry-warden[bot] marked this conversation as resolved.
Outdated

def _is_ignored_error(self, event: "Event", hint: "Hint") -> bool:
exc_info = hint.get("exc_info")
if exc_info is None:
Expand Down
79 changes: 79 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,85 @@ def before_send_transaction(event, hint):
assert event["extra"] == {"before_send_transaction_called": True}


def test_before_send_transaction_span_description_contains_newlines(
sentry_init, capture_events
):
def before_send_transaction(event, hint):
for span in event.get("spans", []):
if (
span.get("description", "")
== "SELECT u.id, u.name, u.email FROM users;"
):
span["description"] = "filtered"
return event

sentry_init(
before_send_transaction=before_send_transaction,
traces_sample_rate=1.0,
)
events = capture_events()

description = "SELECT u.id,\n u.name,\n u.email\n FROM users;"

with start_transaction(name="test_transaction"):
with sentry_sdk.start_span(op="db", description=description):
pass

(event,) = events
assert event["transaction"] == "test_transaction"

spans = event["spans"]
assert len(spans) == 1
assert spans[0]["description"] == "filtered"
assert spans[0]["op"] == "db"


def test_before_send_transaction_span_description_contains_multiple_lines(
sentry_init, capture_events
):
def before_send_transaction(event, hint):
for span in event.get("spans", []):
if (
span.get("description", "")
== "SELECT u.id, u.name, u.email, p.title AS post_title, p.created_at AS post_date FROM users u JOIN posts p ON u.id = p.user_id WHERE u.active = true ORDER BY p.created_at DESC"
):
span["description"] = "no bueno"
return event

sentry_init(
before_send_transaction=before_send_transaction,
traces_sample_rate=1.0,
)
events = capture_events()

description = """SELECT
u.id,
u.name,
u.email,
p.title AS post_title,
p.created_at AS post_date
FROM
users u
JOIN
posts p ON u.id = p.user_id
WHERE
u.active = true
ORDER BY
p.created_at DESC"""

with start_transaction(name="test_transaction"):
with sentry_sdk.start_span(op="db", description=description):
pass

(event,) = events
assert event["transaction"] == "test_transaction"

spans = event["spans"]
assert len(spans) == 1
assert spans[0]["description"] == "no bueno"
assert spans[0]["op"] == "db"


def test_option_before_send_transaction_discard(sentry_init, capture_events):
def before_send_transaction_discard(event, hint):
return None
Expand Down
Loading