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)
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,
},
)
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