From f91a08200bc84eab4df7a57467f1d3d1b8260489 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 19 Nov 2018 22:40:23 +0100 Subject: [PATCH 1/2] test: AWS Lambda on Python 3.7 --- tests/integrations/aws_lambda/test_aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index 8eaa32de8d..b8defc448a 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -57,7 +57,7 @@ def lambda_client(): ) -@pytest.fixture(params=["python3.6", "python2.7"]) +@pytest.fixture(params=["python3.6", "python3.7", "python2.7"]) def run_lambda_function(tmpdir, lambda_client, request, assert_semaphore_acceptance): def inner(code, payload): runtime = request.param From 9f9d5450ad8b7517aa16016fc4e1acba2f295192 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 27 Nov 2018 19:02:39 +0100 Subject: [PATCH 2/2] fix(aws_lambda): Refactor to support Python 3.7 --- scripts/aws-cleanup.sh | 6 ++ sentry_sdk/integrations/aws_lambda.py | 126 ++++++++++++++-------- tests/integrations/aws_lambda/test_aws.py | 1 + 3 files changed, 88 insertions(+), 45 deletions(-) create mode 100644 scripts/aws-cleanup.sh diff --git a/scripts/aws-cleanup.sh b/scripts/aws-cleanup.sh new file mode 100644 index 0000000000..8a55e0668c --- /dev/null +++ b/scripts/aws-cleanup.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Delete all AWS Lambda functions +for func in $(aws lambda list-functions | jq .Functions[].FunctionName); do + echo "Deleting $func" + aws lambda delete-function --function-name $func +done diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 88f5fd3374..b07f9b42f6 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -12,6 +12,46 @@ from sentry_sdk.integrations._wsgi import _filter_headers +def _wrap_handler(handler): + def sentry_handler(event, context, *args, **kwargs): + hub = Hub.current + integration = hub.get_integration(AwsLambdaIntegration) + if integration is None: + return handler(event, context, *args, **kwargs) + + with hub.push_scope() as scope: + with capture_internal_exceptions(): + scope.transaction = context.function_name + scope.add_event_processor(_make_request_event_processor(event, context)) + + try: + return handler(event, context, *args, **kwargs) + except Exception: + exc_info = sys.exc_info() + event, hint = event_from_exception( + exc_info, + client_options=hub.client.options, + mechanism={"type": "aws_lambda", "handled": False}, + ) + hub.capture_event(event, hint=hint) + reraise(*exc_info) + + return sentry_handler + + +def _drain_queue(): + with capture_internal_exceptions(): + hub = Hub.current + integration = hub.get_integration(AwsLambdaIntegration) + if integration is not None: + # Flush out the event queue before AWS kills the + # process. This is not threadsafe. + # make new transport with empty queue + new_transport = hub.client.transport.copy() + hub.client.close() + hub.client.transport = new_transport + + class AwsLambdaIntegration(Integration): identifier = "aws_lambda" @@ -19,66 +59,62 @@ class AwsLambdaIntegration(Integration): def setup_once(): import __main__ as lambda_bootstrap - if not hasattr(lambda_bootstrap, "make_final_handler"): + pre_37 = True # Python 3.6 or 2.7 + + if not hasattr(lambda_bootstrap, "handle_http_request"): + try: + import bootstrap as lambda_bootstrap + + pre_37 = False # Python 3.7 + except ImportError: + pass + + if not hasattr(lambda_bootstrap, "handle_event_request"): logger.warning( "Not running in AWS Lambda environment, " "AwsLambdaIntegration disabled" ) return - import runtime as lambda_runtime + if pre_37: + old_handle_event_request = lambda_bootstrap.handle_event_request - old_make_final_handler = lambda_bootstrap.make_final_handler + def sentry_handle_event_request(request_handler, *args, **kwargs): + request_handler = _wrap_handler(request_handler) + return old_handle_event_request(request_handler, *args, **kwargs) - def sentry_make_final_handler(*args, **kwargs): - handler = old_make_final_handler(*args, **kwargs) + lambda_bootstrap.handle_event_request = sentry_handle_event_request - def sentry_handler(event, context, *args, **kwargs): - hub = Hub.current - integration = hub.get_integration(AwsLambdaIntegration) - if integration is None: - return handler(event, context, *args, **kwargs) + old_handle_http_request = lambda_bootstrap.handle_http_request - with hub.push_scope() as scope: - with capture_internal_exceptions(): - scope.transaction = context.function_name - scope.add_event_processor( - _make_request_event_processor(event, context) - ) + def sentry_handle_http_request(request_handler, *args, **kwargs): + request_handler = _wrap_handler(request_handler) + return old_handle_http_request(request_handler, *args, **kwargs) - try: - return handler(event, context, *args, **kwargs) - except Exception: - exc_info = sys.exc_info() - event, hint = event_from_exception( - exc_info, - client_options=hub.client.options, - mechanism={"type": "aws_lambda", "handled": False}, - ) - hub.capture_event(event, hint=hint) - reraise(*exc_info) + lambda_bootstrap.handle_http_request = sentry_handle_http_request + else: + old_handle_event_request = lambda_bootstrap.handle_event_request - return sentry_handler + def sentry_handle_event_request( + lambda_runtime_client, request_handler, *args, **kwargs + ): + request_handler = _wrap_handler(request_handler) + return old_handle_event_request( + lambda_runtime_client, request_handler, *args, **kwargs + ) - lambda_bootstrap.make_final_handler = sentry_make_final_handler + lambda_bootstrap.handle_event_request = sentry_handle_event_request - old_report_done = lambda_runtime.report_done + # This is the only function that is called in all Python environments + # at the end of the request/response lifecycle. It is the only way to + # do it in the Python 3.7 env. + old_to_json = lambda_bootstrap.to_json - def sentry_report_done(*args, **kwargs): - with capture_internal_exceptions(): - hub = Hub.current - integration = hub.get_integration(AwsLambdaIntegration) - if integration is not None: - # Flush out the event queue before AWS kills the - # process. This is not threadsafe. - # make new transport with empty queue - new_transport = hub.client.transport.copy() - hub.client.close() - hub.client.transport = new_transport - - return old_report_done(*args, **kwargs) - - lambda_runtime.report_done = sentry_report_done + def sentry_to_json(*args, **kwargs): + _drain_queue() + return old_to_json(*args, **kwargs) + + lambda_bootstrap.to_json = sentry_to_json def _make_request_event_processor(aws_event, aws_context): diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index b8defc448a..65b15c165c 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -34,6 +34,7 @@ def shutdown(self, timeout, callback=None): # failing. for event in self._queue: print("EVENT:", json.dumps(event)) + del self._queue[:] def init_sdk(**extra_init_args): sentry_sdk.init(