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
154 changes: 126 additions & 28 deletions sentry_sdk/integrations/chalice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
from functools import wraps

import sentry_sdk
import sentry_sdk.traces
from sentry_sdk.consts import OP
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.integrations.aws_lambda import _make_request_event_processor
from sentry_sdk.integrations.cloud_resource_context import (
CLOUD_PLATFORM,
CLOUD_PROVIDER,
)
from sentry_sdk.traces import SegmentSource, SpanStatus
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 @@ -40,18 +49,42 @@
scope.add_event_processor(
_make_request_event_processor(event, context, configured_time)
)
try:
return ChaliceEventSourceHandler.__call__(self, event, context)
except Exception:
exc_info = sys.exc_info()
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},

if has_span_streaming_enabled(client.options):
span = sentry_sdk.traces.start_span(
name=context.function_name,
parent_span=None,
attributes=_get_lambda_span_attributes(context),
Comment thread
ericapisani marked this conversation as resolved.
)
sentry_sdk.capture_event(event, hint=hint)
client.flush()
reraise(*exc_info)
Comment on lines -43 to -54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we need to do anything here, just leave as is -- this code doesn't do any tracing so we don't need to change it

The set of spans we're creating should be the same as in the legacy path, we don't want any new spans when streaming

try:
return ChaliceEventSourceHandler.__call__(self, event, context)
except Exception:
exc_info = sys.exc_info()
span.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)
reraise(*exc_info)
finally:
span.end()
client.flush()

else:
try:
return ChaliceEventSourceHandler.__call__(self, event, context)
except Exception:
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)
client.flush()
reraise(*exc_info)


def _get_view_function_response(
Expand All @@ -63,38 +96,81 @@
with sentry_sdk.isolation_scope() as scope:
with capture_internal_exceptions():
configured_time = app.lambda_context.get_remaining_time_in_millis()
scope.set_transaction_name(
app.lambda_context.function_name,
source=TransactionSource.COMPONENT,
)

scope.add_event_processor(
_make_request_event_processor(
app.current_request.to_dict(),
app.lambda_context,
configured_time,
)
)
try:
return view_function(**function_args)
except Exception as exc:
if isinstance(exc, ChaliceViewError):

if has_span_streaming_enabled(client.options):
aws_context = app.lambda_context
request_dict = app.current_request.to_dict()
headers = request_dict.get("headers", {})

header_attrs: "Dict[str, Any]" = {}
for header, value in _filter_headers(
Comment thread
ericapisani marked this conversation as resolved.
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"]

span = sentry_sdk.traces.start_span(
name=aws_context.function_name,
parent_span=None,
attributes={
**_get_lambda_span_attributes(aws_context),
**header_attrs,
**additional_attrs,
},
)
Comment on lines +122 to +130
Copy link
Copy Markdown
Contributor

@sentrivana sentrivana Jun 5, 2026

Choose a reason for hiding this comment

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

This is also a new span compared to the legacy path -- please remove

As I understand, this integration is built on top of the AWS Lambda integration similar to e.g. Flask and WSGI? In that case, the pattern is usually that the parent integration creates the transaction/segment encompassing the whole request-response cycle, and the child integration then augments it with info specific to the child integration (e.g. better transaction name and source). The child integration may also create its own spans (e.g. for middlewares, DB queries etc.), but it shouldn't double-instrument the same thing as the top level segment

All of this is to say -- if we don't explicitly create a span in the legacy path, we shouldn't do it in span streaming either

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In this integration specifically, we seem to just want to augment whatever transaction is currently running with the same data as in the AWS Lambda integration.

The question for me is, does this integration even make sense standalone? Because if not, then we don't need to do any sort of post-enrichment here, as it'll already have happened in the AWS Lambda integration. Otherwise, we need to duplicate the enriching logic, but apply it to the current segment instead of creating a new span.

try:
return view_function(**function_args)
except Exception as exc:
if isinstance(exc, ChaliceViewError):
raise

Check warning on line 135 in sentry_sdk/integrations/chalice.py

View check run for this annotation

@sentry/warden / warden: find-bugs

Missing `continue_trace` call before `start_span` breaks distributed tracing in streaming path

In the streaming path, `sentry_sdk.traces.continue_trace(headers)` is never called before `start_span`, so incoming `sentry-trace`/`traceparent`/`baggage` headers from HTTP requests are never propagated — every Chalice invocation starts a fresh, unconnected trace instead of joining the upstream trace. Add `sentry_sdk.traces.continue_trace(headers)` before the `start_span` call (mirroring the GCP integration at `gcp.py:133`).
exc_info = sys.exc_info()
span.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)
raise
exc_info = sys.exc_info()
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},
finally:
span.end()
client.flush()
else:
scope.set_transaction_name(
app.lambda_context.function_name,
source=TransactionSource.COMPONENT,
)
sentry_sdk.capture_event(event, hint=hint)
client.flush()
raise
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)
client.flush()
raise

return wrapped_view_function # type: ignore


class ChaliceIntegration(Integration):
identifier = "chalice"
origin = f"auto.function.{identifier}"

@staticmethod
def setup_once() -> None:
Expand Down Expand Up @@ -129,3 +205,25 @@
RestAPIEventHandler._get_view_function_response = sentry_event_response
# for everything else (like events)
chalice.app.EventSourceHandler = EventSourceHandler


def _get_lambda_span_attributes(aws_context: "Any") -> "Dict[str, Any]":
invoked_arn = aws_context.invoked_function_arn
split_invoked_arn = invoked_arn.split(":")
aws_region = split_invoked_arn[3] if len(split_invoked_arn) > 3 else "unknown"
Comment on lines +211 to +213
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Although AWS sets this value and it's unlikely to be malformed, these extra guards were added to ensure that if it were, we don't crash the user's application


return {
"sentry.op": OP.FUNCTION_AWS,
"sentry.origin": ChaliceIntegration.origin,
"sentry.span.source": SegmentSource.COMPONENT,
"cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA,
"cloud.provider": CLOUD_PROVIDER.AWS,
"faas.name": aws_context.function_name,
"cloud.region": aws_region,
"cloud.resource_id": invoked_arn,
"aws.lambda.invoked_arn": invoked_arn,
"faas.invocation_id": aws_context.aws_request_id,
"faas.version": aws_context.function_version,
"aws.log.group.names": [aws_context.log_group_name],
"aws.log.stream.names": [aws_context.log_stream_name],
}
1 change: 1 addition & 0 deletions sentry_sdk/integrations/cloud_resource_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CLOUD_PLATFORM: # noqa: N801
"""

AWS_EC2 = "aws_ec2"
AWS_LAMBDA = "aws_lambda"
GCP_COMPUTE_ENGINE = "gcp_compute_engine"


Expand Down
Loading
Loading