Skip to content
Merged
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
24 changes: 17 additions & 7 deletions sentry_sdk/integrations/bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,29 +192,39 @@ def size_of_file(self, file: "FileUpload") -> int:
return file.content_length


def _set_segment_name_and_source(transaction_style: str) -> None:
def _get_transaction_name(transaction_style: str, request: "Any") -> str:
try:
if transaction_style == "url":
name = bottle_request.route.rule or "bottle request"
return request.route.rule or "bottle request"
else:
name = (
bottle_request.route.name
or transaction_from_function(bottle_request.route.callback)
return (
request.route.name
or transaction_from_function(request.route.callback)
or "bottle request"
)
except RuntimeError:
return "bottle request"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing route breaks event processor

Medium Severity

_get_transaction_name only catches RuntimeError, so a bound request with no route yet raises AttributeError on .rule or .name. That failure is new in the event processor and aborts it, so events from before_request hooks or unmatched routes lose request data and never receive a fallback transaction name.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ebb39c1. Configure here.



def _set_segment_name_and_source(transaction_style: str) -> None:
name = _get_transaction_name(transaction_style, bottle_request)
if name:
sentry_sdk.get_current_scope().set_transaction_name(
name,
source=SEGMENT_SOURCE_FOR_STYLE[transaction_style],
)
except RuntimeError:
pass


def _make_request_event_processor(
app: "Bottle", request: "LocalRequest", integration: "BottleIntegration"
) -> "EventProcessor":
def event_processor(event: "Event", hint: "dict[str, Any]") -> "Event":
name = _get_transaction_name(integration.transaction_style, request)
event["transaction"] = name
event["transaction_info"] = {
"source": SEGMENT_SOURCE_FOR_STYLE[integration.transaction_style]
}

with capture_internal_exceptions():
BottleRequestExtractor(request).extract_into_event(event)

Expand Down
28 changes: 28 additions & 0 deletions tests/integrations/bottle/test_bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,34 @@ def index():
assert "data" not in event.get("_meta", {}).get("request", {})


def test_error_event_has_transaction_name(sentry_init, capture_events):
"""
Error events captured during request handling should have the correct
transaction name set by the event processor, even though the inline
set_transaction_name call happens after the handler returns.
"""
sentry_init(integrations=[BottleIntegration(transaction_style="url")])
events = capture_events()

app = Bottle()

@app.route("/error/<error_id>")
def error_handler(error_id):
1 / 0

client = Client(app)

try:
client.get("/error/123")
except ZeroDivisionError:
pass

(event,) = events
assert event["exception"]["values"][0]["type"] == "ZeroDivisionError"
assert event["transaction"] == "/error/<error_id>"
assert event["transaction_info"] == {"source": "route"}


def test_oversized_request_body_not_annotated_data_collection(
sentry_init, capture_events, app, get_client
):
Expand Down
Loading