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
117 changes: 50 additions & 67 deletions sentry_sdk/integrations/chalice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.integrations.aws_lambda import _make_request_event_processor
from sentry_sdk.traces import (
SegmentNameSource,
SpanStatus,
StreamedSpan,
get_current_span,
)
from sentry_sdk.tracing import TransactionSource
from sentry_sdk.tracing_utils import has_span_streaming_enabled
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
Expand Down Expand Up @@ -79,73 +77,58 @@ def wrapped_view_function(**function_args: "Any") -> "Any":
)
)

if has_span_streaming_enabled(client.options):
current_span = get_current_span()
segment = None
if type(current_span) is StreamedSpan:
# A segment already exists (created by the AWS Lambda
# integration), so decorate it with Chalice attributes
# The AWS Lambda integration owns the span lifecycle
# (end + flush), but Chalice converts unhandled view exceptions
# into 500 responses, so the error must be captured here.
request_dict = app.current_request.to_dict()
headers = request_dict.get("headers", {})

header_attrs: "Dict[str, Any]" = {}
for header, value in _filter_headers(
headers, use_annotated_value=False
).items():
header_attrs[f"http.request.header.{header.lower()}"] = value

additional_attrs: "Dict[str, Any]" = {}
if "method" in request_dict:
additional_attrs["http.request.method"] = request_dict["method"]

attributes = {
"sentry.origin": ChaliceIntegration.origin,
**header_attrs,
**additional_attrs,
}

segment = current_span._segment
segment.set_attributes(attributes)

try:
return view_function(**function_args)
except Exception as exc:
if isinstance(exc, ChaliceViewError):
raise
exc_info = sys.exc_info()
if segment:
segment.status = SpanStatus.ERROR.value
sentry_event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},
)
sentry_sdk.capture_event(sentry_event, hint=hint)
if segment is None:
client.flush()
scope.set_transaction_name(
app.lambda_context.function_name,
source=SegmentNameSource.COMPONENT,
)

current_span = sentry_sdk.traces.get_current_span()
segment = None
if type(current_span) is StreamedSpan:
# A segment already exists (created by the AWS Lambda
# integration), so decorate it with Chalice attributes
# The AWS Lambda integration owns the span lifecycle
# (end + flush), but Chalice converts unhandled view exceptions
# into 500 responses, so the error must be captured here.
request_dict = app.current_request.to_dict()
headers = request_dict.get("headers", {})

header_attrs: "Dict[str, Any]" = {}
for header, value in _filter_headers(
headers, use_annotated_value=False
).items():
header_attrs[f"http.request.header.{header.lower()}"] = value

additional_attrs: "Dict[str, Any]" = {}
if "method" in request_dict:
additional_attrs["http.request.method"] = request_dict["method"]

attributes = {
"sentry.origin": ChaliceIntegration.origin,
**header_attrs,
**additional_attrs,
}

segment = current_span._segment
segment.set_attributes(attributes)

try:
return view_function(**function_args)
except Exception as exc:
if isinstance(exc, ChaliceViewError):
raise
else:
scope.set_transaction_name(
app.lambda_context.function_name,
source=TransactionSource.COMPONENT,
exc_info = sys.exc_info()
if segment:
segment.status = SpanStatus.ERROR.value
sentry_event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},
)
try:
return view_function(**function_args)
except Exception as exc:
if isinstance(exc, ChaliceViewError):
raise
exc_info = sys.exc_info()
sentry_event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},
)
sentry_sdk.capture_event(sentry_event, hint=hint)
sentry_sdk.capture_event(sentry_event, hint=hint)
if segment is None:
client.flush()
raise
raise

return wrapped_view_function # type: ignore

Expand Down
38 changes: 8 additions & 30 deletions tests/integrations/chalice/test_chalice.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ def _generate_lambda_context(self):

@pytest.fixture
def app(sentry_init):
sentry_init(integrations=[ChaliceIntegration()])
sentry_init(
integrations=[ChaliceIntegration()],
traces_sample_rate=1.0,
trace_lifecycle="stream",
)
app = Chalice(app_name="sentry_chalice")

@app.route("/boom")
Expand Down Expand Up @@ -175,35 +179,9 @@ def test_transaction(
assert event["transaction_info"] == {"source": expected_source}


def _make_span_streaming_app(sentry_init):
sentry_init(
integrations=[ChaliceIntegration()],
traces_sample_rate=1.0,
trace_lifecycle="stream",
)
app = Chalice(app_name="sentry_chalice")

@app.route("/message")
def hi():
capture_message("hi")
return {"status": "ok"}

@app.route("/boom")
def boom():
raise Exception("boom goes the dynamite!")

LocalGateway._generate_lambda_context = _generate_lambda_context

return app


def test_span_streaming_existing_span(
sentry_init,
capture_items,
):
def test_existing_span(sentry_init, capture_items, app):
"""When a segment already exists (e.g. created by the AWS Lambda
integration), Chalice decorates it instead of creating a duplicate."""
app = _make_span_streaming_app(sentry_init)
client = RequestHandler(app)
items = capture_items("span")

Expand Down Expand Up @@ -232,11 +210,11 @@ def test_span_streaming_existing_span(
assert span["status"] == "ok"


def test_span_streaming_existing_span_error(
def test_existing_span_error(
sentry_init,
capture_items,
app,
):
app = _make_span_streaming_app(sentry_init)
client = RequestHandler(app)
items = capture_items("event", "span")

Expand Down
Loading