From 9fc3bd2375cd2b7bff4c40dc21df3738adab14d8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 5 Sep 2024 14:51:26 +0200 Subject: [PATCH] Fix AWS Lambda tests (#3495) AWS changed their Lambda run times, so we no longer have access to the current exception during the init phase of the Lambda function. I am trying to fix this upstream: aws/aws-lambda-python-runtime-interface-client#172 This PR adds a fall back to the errror json object provided by AWS. This has way less data than a real exception in it, but it is better than nothing. Fixes #3464 --- sentry_sdk/integrations/aws_lambda.py | 62 +++++++++++++++++++++++ tests/integrations/aws_lambda/test_aws.py | 21 ++++---- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 168b8061aa..f0cdf31f8c 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -1,3 +1,5 @@ +import json +import re import sys from copy import deepcopy from datetime import datetime, timedelta, timezone @@ -56,6 +58,11 @@ def sentry_init_error(*args, **kwargs): ) sentry_sdk.capture_event(sentry_event, hint=hint) + else: + # Fall back to AWS lambdas JSON representation of the error + sentry_event = _event_from_error_json(json.loads(args[1])) + sentry_sdk.capture_event(sentry_event) + return init_error(*args, **kwargs) return sentry_init_error # type: ignore @@ -428,3 +435,58 @@ def _get_cloudwatch_logs_url(aws_context, start_time): ) return url + + +def _parse_formatted_traceback(formatted_tb): + # type: (list[str]) -> list[dict[str, Any]] + frames = [] + for frame in formatted_tb: + match = re.match(r'File "(.+)", line (\d+), in (.+)', frame.strip()) + if match: + file_name, line_number, func_name = match.groups() + line_number = int(line_number) + frames.append( + { + "filename": file_name, + "function": func_name, + "lineno": line_number, + "vars": None, + "pre_context": None, + "context_line": None, + "post_context": None, + } + ) + return frames + + +def _event_from_error_json(error_json): + # type: (dict[str, Any]) -> Event + """ + Converts the error JSON from AWS Lambda into a Sentry error event. + This is not a full fletched event, but better than nothing. + + This is an example of where AWS creates the error JSON: + https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/2.2.1/awslambdaric/bootstrap.py#L479 + """ + event = { + "level": "error", + "exception": { + "values": [ + { + "type": error_json.get("errorType"), + "value": error_json.get("errorMessage"), + "stacktrace": { + "frames": _parse_formatted_traceback( + error_json.get("stackTrace", []) + ), + }, + "mechanism": { + "type": "aws_lambda", + "handled": False, + }, + } + ], + }, + } # type: Event + + return event diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index ffcaf877d7..cc62b7e7ad 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -36,6 +36,13 @@ import pytest +RUNTIMES_TO_TEST = [ + "python3.8", + "python3.9", + "python3.10", + "python3.11", + "python3.12", +] LAMBDA_PRELUDE = """ from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration, get_lambda_bootstrap @@ -137,15 +144,7 @@ def lambda_client(): return get_boto_client() -@pytest.fixture( - params=[ - "python3.8", - "python3.9", - "python3.10", - "python3.11", - "python3.12", - ] -) +@pytest.fixture(params=RUNTIMES_TO_TEST) def lambda_runtime(request): return request.param @@ -331,7 +330,9 @@ def test_init_error(run_lambda_function, lambda_runtime): syntax_check=False, ) - (event,) = envelope_items + # We just take the last one, because it could be that in the output of the Lambda + # invocation there is still the envelope of the previous invocation of the function. + event = envelope_items[-1] assert event["exception"]["values"][0]["value"] == "name 'func' is not defined"