From b9e1d77dea8cd1ed91a6f0547c53e4467c21c537 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Tue, 27 Oct 2026 11:28:19 +0100 Subject: [PATCH 1/3] . --- scripts/init_serverless_sdk.py | 36 +++++-------------- .../lambda_functions/PathHandler/app/index.py | 8 +++++ .../aws_lambda/test_aws_lambda.py | 13 +++++++ tests/integrations/aws_lambda/utils.py | 7 +++- 4 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py diff --git a/scripts/init_serverless_sdk.py b/scripts/init_serverless_sdk.py index d27036af48..5423ba19bd 100644 --- a/scripts/init_serverless_sdk.py +++ b/scripts/init_serverless_sdk.py @@ -6,9 +6,8 @@ 'sentry_sdk.integrations.init_serverless_sdk.sentry_lambda_handler' """ +import importlib import os -import re -import sys from typing import TYPE_CHECKING import sentry_sdk @@ -27,8 +26,6 @@ class AWSLambdaModuleLoader: - DIR_PATH_REGEX = r"^(.+)\/([^\/]+)$" - def __init__(self, sentry_initial_handler): try: module_path, self.handler_name = sentry_initial_handler.rsplit(".", 1) @@ -41,35 +38,18 @@ def extract_and_load_lambda_function_module(self, module_path): """ Method that extracts and loads lambda function module from module_path """ - py_version = sys.version_info - - if re.match(self.DIR_PATH_REGEX, module_path): - # With a path like -> `scheduler/scheduler/event` - # `module_name` is `event`, and `module_file_path` is `scheduler/scheduler/event.py` - module_name = module_path.split(os.path.sep)[-1] - module_file_path = module_path + ".py" - - # Supported python versions are 3.6, 3.7, 3.8 - if py_version >= (3, 6): - import importlib.util - - spec = importlib.util.spec_from_file_location( - module_name, module_file_path - ) - self.lambda_function_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(self.lambda_function_module) - else: - raise ValueError("Python version %s is not supported." % py_version) - else: - import importlib - - self.lambda_function_module = importlib.import_module(module_path) + # like the Lambda runtime: `dir/file` is the module `dir.file`, which `sys.modules` + # keeps, so its code only runs on the first invocation. + # https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/4.2.2/awslambdaric/bootstrap.py#L53 + self.lambda_function_module = importlib.import_module( + module_path.replace("/", ".") + ) def get_lambda_handler(self): return getattr(self.lambda_function_module, self.handler_name) -def sentry_lambda_handler(event: "Any", context: "Any") -> None: +def sentry_lambda_handler(event: "Any", context: "Any") -> "Any": """ Handler function that invokes a lambda handler which path is defined in environment variables as "SENTRY_INITIAL_HANDLER" diff --git a/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py new file mode 100644 index 0000000000..694df0a7fe --- /dev/null +++ b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py @@ -0,0 +1,8 @@ +# counts the invocations since this module was loaded. +invocations = 0 + + +def handler(event, context): + global invocations + invocations += 1 + return {"invocations": invocations} diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 0bf1cd3670..63d58b755d 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -201,6 +201,19 @@ def test_init_error(lambda_client, test_environment): assert transaction_event["transaction"] == "InitError" +def test_path_handler_loaded_once(lambda_client, test_environment): + invocations = [ + json.loads( + lambda_client.invoke(FunctionName="PathHandler", Payload=json.dumps({}))[ + "Payload" + ].read() + )["invocations"] + for _ in range(2) + ] + # the Sentry layer loads the function's module once, not on every invocation. + assert invocations == [1, 2] + + def test_timeout_error_scope_modified(lambda_client, test_environment): lambda_client.invoke( FunctionName="TimeoutErrorScopeModified", diff --git a/tests/integrations/aws_lambda/utils.py b/tests/integrations/aws_lambda/utils.py index 8933d856ed..2d3e3c7fa5 100644 --- a/tests/integrations/aws_lambda/utils.py +++ b/tests/integrations/aws_lambda/utils.py @@ -27,6 +27,9 @@ LAMBDA_FUNCTION_TIMEOUT = 10 SAM_PORT = 3001 +# functions whose handler is a path instead of `index.handler`. +INITIAL_HANDLERS = {"PathHandler": "app/index.handler"} + PYTHON_VERSION = f"python{sys.version_info.major}.{sys.version_info.minor}" # Match the host CPU architecture so local runs on ARM machines (e.g. macOS) @@ -117,7 +120,9 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: "Environment": { "Variables": { "SENTRY_DSN": dsn, - "SENTRY_INITIAL_HANDLER": "index.handler", + "SENTRY_INITIAL_HANDLER": INITIAL_HANDLERS.get( + lambda_dir, "index.handler" + ), "SENTRY_TRACES_SAMPLE_RATE": "1.0", } }, From 989a0b9f9ddb71b911253e53ef1b5bee4d987905 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Tue, 27 Oct 2026 14:00:06 +0100 Subject: [PATCH 2/3] wip - make more descriptive --- scripts/init_serverless_sdk.py | 2 +- .../lambda_functions/PathHandler/app/index.py | 11 ++++++----- tests/integrations/aws_lambda/test_aws_lambda.py | 9 +++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scripts/init_serverless_sdk.py b/scripts/init_serverless_sdk.py index 5423ba19bd..2207997192 100644 --- a/scripts/init_serverless_sdk.py +++ b/scripts/init_serverless_sdk.py @@ -40,7 +40,7 @@ def extract_and_load_lambda_function_module(self, module_path): """ # like the Lambda runtime: `dir/file` is the module `dir.file`, which `sys.modules` # keeps, so its code only runs on the first invocation. - # https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/4.2.2/awslambdaric/bootstrap.py#L53 + # https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/fa2462d93d11da632aad8f8bfb07a3e060438150/awslambdaric/bootstrap.py#L53 self.lambda_function_module = importlib.import_module( module_path.replace("/", ".") ) diff --git a/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py index 694df0a7fe..0a28a52195 100644 --- a/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py +++ b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py @@ -1,8 +1,9 @@ -# counts the invocations since this module was loaded. -invocations = 0 +# counts the invocations since this module was loaded; gets reset on cold start or +# when the module is reloaded. +num_invocations = 0 def handler(event, context): - global invocations - invocations += 1 - return {"invocations": invocations} + global num_invocations + num_invocations += 1 + return {"invocations": num_invocations} diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 63d58b755d..ad402d347c 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -202,16 +202,17 @@ def test_init_error(lambda_client, test_environment): def test_path_handler_loaded_once(lambda_client, test_environment): - invocations = [ + num_invocations = [ json.loads( lambda_client.invoke(FunctionName="PathHandler", Payload=json.dumps({}))[ "Payload" ].read() - )["invocations"] + )["num_invocations"] for _ in range(2) ] - # the Sentry layer loads the function's module once, not on every invocation. - assert invocations == [1, 2] + # Sentry layer should load the function's module once (on cold start or whenever + # the module is reloaded), not on every warm invocation. + assert num_invocations == [1, 2] def test_timeout_error_scope_modified(lambda_client, test_environment): From fac06dc59f28563f7aa21c2d9f195493d09e45f1 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Tue, 27 Oct 2026 14:20:59 +0100 Subject: [PATCH 3/3] fix test --- .../aws_lambda/lambda_functions/PathHandler/app/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py index 0a28a52195..c589df7c4c 100644 --- a/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py +++ b/tests/integrations/aws_lambda/lambda_functions/PathHandler/app/index.py @@ -6,4 +6,4 @@ def handler(event, context): global num_invocations num_invocations += 1 - return {"invocations": num_invocations} + return {"num_invocations": num_invocations}