diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..1990420
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+_Issue #, if available:_
+
+_Description of changes:_
+
+_Target (OCI, Managed Runtime, both):_
+
+By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml
index fe17cda..5b80d23 100644
--- a/.github/workflows/test-on-push-and-pr.yml
+++ b/.github/workflows/test-on-push-and-pr.yml
@@ -11,12 +11,38 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v2
-      env:
-        GITHUB_WORKSPACE: /
-    - name: Set up python
-      uses: actions/setup-python@v2
-      with:
-        python-version: '3.8'
+    - uses: actions/checkout@v4
     - name: Run 'pr' target
       run: make pr
+
+  alpine:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Run alpine integration tests
+      run: DISTRO=alpine make test-integ
+
+  amazonlinux:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Run amazonlinux integration tests
+      run: DISTRO=amazonlinux make test-integ
+
+  debian:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Run debian integration tests
+      run: DISTRO=debian make test-integ
+
+  ubuntu:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Run ubuntu integration tests
+      run: DISTRO=ubuntu make test-integ
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 116767e..9d46e4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@ generated.docker-compose.*.yml
 
 tests/integration/resources/init
 
+.idea
+
 node_modules/
 *.tsbuildinfo
 
@@ -146,3 +148,8 @@ cython_debug/
 
 # Test files generated
 tmp*.py
+
+# dependencies
+deps/artifacts/
+deps/aws-lambda-cpp-*/
+deps/curl-*/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index dcabbf4..bb66b03 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,5 +3,5 @@ repos:
     rev: 19.3b0
     hooks:
       - id: black
-        language_version: python3.6
+        language_version: python3.9
         exclude_types: ['markdown', 'ini', 'toml', 'rst']
diff --git a/Makefile b/Makefile
index 27576e8..521b61c 100644
--- a/Makefile
+++ b/Makefile
@@ -17,11 +17,11 @@ setup-codebuild-agent:
 
 .PHONY: test-smoke
 test-smoke: setup-codebuild-agent
-	CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.1.yml alpine 3.12 3.8
+	CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 3.9
 
 .PHONY: test-integ
 test-integ: setup-codebuild-agent
-	CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/.
+	CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/.
 
 .PHONY: check-security
 check-security:
@@ -41,7 +41,10 @@ dev: init test
 
 # Verifications to run before sending a pull request
 .PHONY: pr
-pr: init check-format check-security dev test-smoke
+pr: init check-format check-security dev
+
+codebuild: setup-codebuild-agent
+	CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild
 
 .PHONY: clean
 clean:
diff --git a/README.md b/README.md
index 63cbcb5..4a96a3f 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,12 @@ We have open-sourced a set of software packages, Runtime Interface Clients (RIC)
   base images to be Lambda compatible.
 The Lambda Runtime Interface Client is a lightweight interface that allows your runtime to receive requests from and send requests to the Lambda service.
 
-The Lambda Python Runtime Interface Client is vended through [pip](https://pypi.org/project/awslambdaric). 
+The Lambda Python Runtime Interface Client is vended through [pip](https://pypi.org/project/awslambdaric).
 You can include this package in your preferred base image to make that base image Lambda compatible.
 
 ## Requirements
 The Python Runtime Interface Client package currently supports Python versions:
- - 3.6.x up to and including 3.9.x
+ - 3.9.x up to and including 3.13.x
 
 ## Usage
 
@@ -19,7 +19,6 @@ First step is to choose the base image to be used. The supported Linux OS distri
 
  - Amazon Linux 2
  - Alpine
- - CentOS
  - Debian
  - Ubuntu
 
@@ -58,7 +57,7 @@ Example Dockerfile (to keep the image light we use a multi-stage build):
 # Define custom function directory
 ARG FUNCTION_DIR="/function"
 
-FROM python:buster as build-image
+FROM public.ecr.aws/docker/library/python:buster as build-image
 
 # Include global arg in this stage of the build
 ARG FUNCTION_DIR
@@ -82,7 +81,7 @@ RUN pip install \
         awslambdaric
 
 
-FROM python:buster
+FROM public.ecr.aws/docker/library/python:buster
 
 # Include global arg in this stage of the build
 ARG FUNCTION_DIR
@@ -104,18 +103,18 @@ def handler(event, context):
 
 ### Local Testing
 
-To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator. 
+To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator.
 
 *To install the emulator and test your Lambda function*
 
-1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine. 
+1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine.
 
 ```shell script
 mkdir -p ~/.aws-lambda-rie && \
     curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \
     chmod +x ~/.aws-lambda-rie/aws-lambda-rie
 ```
-2) Run your Lambda image function using the docker run command. 
+2) Run your Lambda image function using the docker run command.
 
 ```shell script
 docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
@@ -124,9 +123,9 @@ docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
         /usr/local/bin/python -m awslambdaric app.handler
 ```
 
-This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`. 
+This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`.
 
-3) Post an event to the following endpoint using a curl command: 
+3) Post an event to the following endpoint using a curl command:
 
 ```shell script
 curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
diff --git a/RELEASE.CHANGELOG.md b/RELEASE.CHANGELOG.md
new file mode 100644
index 0000000..fc45791
--- /dev/null
+++ b/RELEASE.CHANGELOG.md
@@ -0,0 +1,172 @@
+### May 26, 2025
+`3.1.1`
+- Move unhandled exception warning message to init errors. ([#189](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/189))
+
+### May 21, 2025
+`3.1.0`
+- Add support for multi tenancy ([#187](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/187))
+
+### February 27, 2024
+`3.0.2`
+- Update `simplejson` to `3.20.1`([#184](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/184))
+
+### January 27, 2024
+`3.0.1`
+- Don't enforce text format on uncaught exception warning message ([#182](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/182))
+
+### November 19, 2024
+`3.0.0`
+- Drop support for deprecated python versions ([#179](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/179))
+- Add support for snapstart runtime hooks ([#176](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/176))
+
+### August 23, 2024
+`2.2.1`:
+- Patch libcurl configure.ac to work with later versions of autoconf ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/168))
+
+### August 8, 2024
+
+`2.2.0`:
+
+- Propogate error type in header when reporting init error to RAPID ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/166))
+
+### July 31, 2024
+
+`2.1.0`:
+
+- Raise all init errors in init instead of suppressing them until the first invoke ([#163](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/163))
+
+### June 19, 2024
+
+`2.0.12`:
+
+- Relax simplejson dependency and keep it backwards compatible ([#153](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/152))
+
+### March 27, 2024
+
+`2.0.11`:
+
+- Upgrade simplejson to 3.18.4 ([#136](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/136))
+
+### February 13, 2024
+
+`2.0.10`:
+
+- Update format of unhandled exception warning message. ([#132](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/132))
+
+### February 01, 2024
+
+`2.0.9`:
+
+- Log warning on unhandled exceptions. ([#120](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/120))
+
+### October 30, 2023
+
+`2.0.8`:
+
+- Onboarded Python3.12 ([#118](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/118))
+- Fix runtime_client blocking main thread from SIGTERM being handled. Enabled by default only for Python3.12 ([#115](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/115)) ([#124](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/124)) ([#125](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/125))
+- Use unicode chars instead of escape sequences in json encoder output. Enabled by default only for Python3.12 ([#88](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/88)) ([#122](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/122))
+- Cold start improvements ([#121](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/121))
+
+### August 29, 2023
+
+`2.0.7`:
+
+- Allow already structured logs in text format to use level-specific headers for logging protocol ([#111](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/111))
+
+### August 22, 2023
+
+`2.0.6`:
+
+- Add structured logging implementation ([#101](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/101))
+
+### August 16, 2023
+
+`2.0.5`:
+
+- Add support for Python3.11. ([#103](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/103))
+- Add support for Python3.10. ([#102](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/102))
+- Emit multi-line logs with timestamps.([#92](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/92))
+- Remove importlib-metadata dependency.([#83](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/83))
+
+### May 25, 2022
+
+`2.0.4`:
+
+- Update os distro and runtime versions in compatibility tests, source base images from Amazon ECR Public ([#80](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/80))
+- Improve error output for missing handler ([#70](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/70))
+- Update curl to 7.83.1 ([#79](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/79))
+
+### May 4, 2022
+
+`2.0.3`:
+
+- Add changelog ([#75](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/75))
+- Fix curl download url ([#74](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/74))
+- Update curl to 7.83.0 ([#72](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/72))
+
+### Apr 7, 2022
+
+`2.0.2`:
+
+- Add leading zeros to the milliseconds part of a log timestamp ([#13](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/13))
+- Use the raw fd directly rather than opening the fd pseudo file ([#56](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/56))
+
+### Jan 4, 2022
+
+`2.0.1`:
+
+- Add '--no-same-owner' option to all scripts tar commands ([#37](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/37))
+
+### Sep 29, 2021
+
+`2.0.0`:
+
+- Add arm64 architecture support ([#59](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/59))
+- Update Curl to 7.78.0 ([#52](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/52))
+
+### Aug 23, 2021
+
+`1.2.2`:
+
+- Remove importlib.metadata dependency ([#55](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/55))
+
+### Aug 20, 2021
+
+`1.2.1`:
+
+- Remove logging for handler directory, as its adding un-necessary cloudwatch cost ([#51](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/51))
+
+### Jun 28, 2021
+
+`1.2.0`:
+
+- Move the `/` to `.` replacement only for import_module call ([#47](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/47))
+- Add support for `/` in handler name ([#45](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/45))
+- Add requestId in error response ([#40](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/40))
+
+### Jun 9, 2021
+
+`1.1.1`:
+
+- Update Curl version to 7.77.0 ([#33](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/35))
+
+### May 28, 2021
+
+`1.1.0`:
+
+- Release GIL when polling Runtime API for next invocation ([#33](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/33))
+- Use importlib instead of deprecated imp module ([#28](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/28))
+- Rename test directory ([#21](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/21))
+- Revise fetching latest patch version of python ([#9](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/9))
+- Update README.md examples: remove period from curl command ([#7](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/7))
+- Add 'docker login' to fix pull rate limit issue ([#5](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/5))
+- Include GitHub action on push and pr ([#3](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/3))
+- Use Python 3.6 for Black ([#2](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/2))
+- Tidy up setup.py ([#1](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/1))
+
+### Dec 01, 2020
+
+`1.0.0`:
+
+- Initial release of AWS Lambda Python Runtime Interface Client
diff --git a/THIRD-PARTY-LICENSES b/THIRD-PARTY-LICENSES
index 1d67f19..41cde9b 100644
--- a/THIRD-PARTY-LICENSES
+++ b/THIRD-PARTY-LICENSES
@@ -228,15 +228,15 @@ SOFTWARE.
 
 ------
 
-** libcurl; version 7.65.3 -- https://github.com/curl/curl
-Copyright (c) 1996 - 2020, Daniel Stenberg, <daniel@haxx.se>, and many
+** libcurl; version 7.83.1 -- https://github.com/curl/curl
+Copyright (c) 1996 - 2022, Daniel Stenberg, daniel@haxx.se, and many
 contributors, see the THANKS file.
 
 All rights reserved.
 
 COPYRIGHT AND PERMISSION NOTICE
 
-Copyright (c) 1996 - 2020, Daniel Stenberg, <daniel@haxx.se>, and many
+Copyright (c) 1996 - 2022, Daniel Stenberg, daniel@haxx.se, and many
 contributors, see the THANKS file.
 
 All rights reserved.
diff --git a/awslambdaric/__init__.py b/awslambdaric/__init__.py
index e69de29..5605903 100644
--- a/awslambdaric/__init__.py
+++ b/awslambdaric/__init__.py
@@ -0,0 +1,5 @@
+"""
+Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+"""
+
+__version__ = "3.1.1"
diff --git a/awslambdaric/__main__.py b/awslambdaric/__main__.py
index b6fe5b8..5cbbaab 100644
--- a/awslambdaric/__main__.py
+++ b/awslambdaric/__main__.py
@@ -10,10 +10,14 @@
 
 def main(args):
     app_root = os.getcwd()
-    handler = args[1]
+
+    try:
+        handler = args[1]
+    except IndexError:
+        raise ValueError("Handler not set")
+
     lambda_runtime_api_addr = os.environ["AWS_LAMBDA_RUNTIME_API"]
 
-    print(f"Executing '{handler}' in function directory '{app_root}'")
     bootstrap.run(app_root, handler, lambda_runtime_api_addr)
 
 
diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py
index be9271f..cb8d5c3 100644
--- a/awslambdaric/bootstrap.py
+++ b/awslambdaric/bootstrap.py
@@ -13,46 +13,56 @@
 from .lambda_context import LambdaContext
 from .lambda_runtime_client import LambdaRuntimeClient
 from .lambda_runtime_exception import FaultException
+from .lambda_runtime_log_utils import (
+    _DATETIME_FORMAT,
+    _DEFAULT_FRAME_TYPE,
+    _JSON_FRAME_TYPES,
+    _TEXT_FRAME_TYPES,
+    JsonFormatter,
+    LogFormat,
+    _format_log_level,
+    _get_log_level_from_env_var,
+)
 from .lambda_runtime_marshaller import to_json
 
 ERROR_LOG_LINE_TERMINATE = "\r"
 ERROR_LOG_IDENT = "\u00a0"  # NO-BREAK SPACE U+00A0
+_AWS_LAMBDA_LOG_FORMAT = LogFormat.from_str(os.environ.get("AWS_LAMBDA_LOG_FORMAT"))
+_AWS_LAMBDA_LOG_LEVEL = _get_log_level_from_env_var(
+    os.environ.get("AWS_LAMBDA_LOG_LEVEL")
+)
+AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"
+INIT_TYPE_SNAP_START = "snap-start"
 
 
 def _get_handler(handler):
     try:
         (modname, fname) = handler.rsplit(".", 1)
     except ValueError as e:
-        fault = FaultException(
+        raise FaultException(
             FaultException.MALFORMED_HANDLER_NAME,
             "Bad handler '{}': {}".format(handler, str(e)),
         )
-        return make_fault_handler(fault)
 
     try:
         if modname.split(".")[0] in sys.builtin_module_names:
-            fault = FaultException(
+            raise FaultException(
                 FaultException.BUILT_IN_MODULE_CONFLICT,
                 "Cannot use built-in module {} as a handler module".format(modname),
             )
-            return make_fault_handler(fault)
         m = importlib.import_module(modname.replace("/", "."))
     except ImportError as e:
-        fault = FaultException(
+        raise FaultException(
             FaultException.IMPORT_MODULE_ERROR,
             "Unable to import module '{}': {}".format(modname, str(e)),
         )
-        request_handler = make_fault_handler(fault)
-        return request_handler
     except SyntaxError as e:
         trace = ['  File "%s" Line %s\n    %s' % (e.filename, e.lineno, e.text)]
-        fault = FaultException(
+        raise FaultException(
             FaultException.USER_CODE_SYNTAX_ERROR,
             "Syntax error in module '{}': {}".format(modname, str(e)),
             trace,
         )
-        request_handler = make_fault_handler(fault)
-        return request_handler
 
     try:
         request_handler = getattr(m, fname)
@@ -62,18 +72,16 @@ def _get_handler(handler):
             "Handler '{}' missing on module '{}'".format(fname, modname),
             None,
         )
-        request_handler = make_fault_handler(fault)
-    return request_handler
-
-
-def make_fault_handler(fault):
-    def result(*args):
         raise fault
-
-    return result
+    return request_handler
 
 
-def make_error(error_message, error_type, stack_trace, invoke_id=None):
+def make_error(
+    error_message,
+    error_type,
+    stack_trace,
+    invoke_id=None,
+):
     result = {
         "errorMessage": error_message if error_message else "",
         "errorType": error_type if error_type else "",
@@ -92,34 +100,52 @@ def replace_line_indentation(line, indent_char, new_indent_char):
     return (new_indent_char * ident_chars_count) + line[ident_chars_count:]
 
 
-def log_error(error_result, log_sink):
-    error_description = "[ERROR]"
+if _AWS_LAMBDA_LOG_FORMAT == LogFormat.JSON:
+    _ERROR_FRAME_TYPE = _JSON_FRAME_TYPES[logging.ERROR]
+
+    def log_error(error_result, log_sink):
+        error_result = {
+            "timestamp": time.strftime(
+                _DATETIME_FORMAT, logging.Formatter.converter(time.time())
+            ),
+            "log_level": "ERROR",
+            **error_result,
+        }
+        log_sink.log_error(
+            [to_json(error_result)],
+        )
 
-    error_result_type = error_result.get("errorType")
-    if error_result_type:
-        error_description += " " + error_result_type
+else:
+    _ERROR_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.ERROR]
 
-    error_result_message = error_result.get("errorMessage")
-    if error_result_message:
+    def log_error(error_result, log_sink):
+        error_description = "[ERROR]"
+
+        error_result_type = error_result.get("errorType")
         if error_result_type:
-            error_description += ":"
-        error_description += " " + error_result_message
+            error_description += " " + error_result_type
+
+        error_result_message = error_result.get("errorMessage")
+        if error_result_message:
+            if error_result_type:
+                error_description += ":"
+            error_description += " " + error_result_message
 
-    error_message_lines = [error_description]
+        error_message_lines = [error_description]
 
-    stack_trace = error_result.get("stackTrace")
-    if stack_trace is not None:
-        error_message_lines += ["Traceback (most recent call last):"]
-        for trace_element in stack_trace:
-            if trace_element == "":
-                error_message_lines += [""]
-            else:
-                for trace_line in trace_element.splitlines():
-                    error_message_lines += [
-                        replace_line_indentation(trace_line, " ", ERROR_LOG_IDENT)
-                    ]
+        stack_trace = error_result.get("stackTrace")
+        if stack_trace is not None:
+            error_message_lines += ["Traceback (most recent call last):"]
+            for trace_element in stack_trace:
+                if trace_element == "":
+                    error_message_lines += [""]
+                else:
+                    for trace_line in trace_element.splitlines():
+                        error_message_lines += [
+                            replace_line_indentation(trace_line, " ", ERROR_LOG_IDENT)
+                        ]
 
-    log_sink.log_error(error_message_lines)
+        log_sink.log_error(error_message_lines)
 
 
 def handle_event_request(
@@ -132,6 +158,7 @@ def handle_event_request(
     cognito_identity_json,
     invoked_function_arn,
     epoch_deadline_time_in_ms,
+    tenant_id,
     log_sink,
 ):
     error_result = None
@@ -142,6 +169,7 @@ def handle_event_request(
             epoch_deadline_time_in_ms,
             invoke_id,
             invoked_function_arn,
+            tenant_id,
         )
         event = lambda_runtime_client.marshaller.unmarshal_request(
             event_body, content_type
@@ -152,7 +180,12 @@ def handle_event_request(
         )
     except FaultException as e:
         xray_fault = make_xray_fault("LambdaValidationError", e.msg, os.getcwd(), [])
-        error_result = make_error(e.msg, e.exception_type, e.trace, invoke_id)
+        error_result = make_error(
+            e.msg,
+            e.exception_type,
+            e.trace,
+            invoke_id,
+        )
 
     except Exception:
         etype, value, tb = sys.exc_info()
@@ -168,6 +201,7 @@ def handle_event_request(
         )
 
     if error_result is not None:
+
         log_error(error_result, log_sink)
         lambda_runtime_client.post_invocation_error(
             invoke_id, to_json(error_result), to_json(xray_fault)
@@ -195,6 +229,7 @@ def create_lambda_context(
     epoch_deadline_time_in_ms,
     invoke_id,
     invoked_function_arn,
+    tenant_id,
 ):
     client_context = None
     if client_context_json:
@@ -209,6 +244,7 @@ def create_lambda_context(
         cognito_identity,
         epoch_deadline_time_in_ms,
         invoked_function_arn,
+        tenant_id,
     )
 
 
@@ -221,7 +257,9 @@ def build_fault_result(exc_info, msg):
             break
 
     return make_error(
-        msg if msg else str(value), etype.__name__, traceback.format_list(tb_tuples)
+        msg if msg else str(value),
+        etype.__name__,
+        traceback.format_list(tb_tuples),
     )
 
 
@@ -250,6 +288,29 @@ def extract_traceback(tb):
     ]
 
 
+def on_init_complete(lambda_runtime_client, log_sink):
+    from . import lambda_runtime_hooks_runner
+
+    try:
+        lambda_runtime_hooks_runner.run_before_snapshot()
+        lambda_runtime_client.restore_next()
+    except:
+        error_result = build_fault_result(sys.exc_info(), None)
+        log_error(error_result, log_sink)
+        lambda_runtime_client.post_init_error(
+            error_result, FaultException.BEFORE_SNAPSHOT_ERROR
+        )
+        sys.exit(64)
+
+    try:
+        lambda_runtime_hooks_runner.run_after_restore()
+    except:
+        error_result = build_fault_result(sys.exc_info(), None)
+        log_error(error_result, log_sink)
+        lambda_runtime_client.report_restore_error(error_result)
+        sys.exit(65)
+
+
 class LambdaLoggerHandler(logging.Handler):
     def __init__(self, log_sink):
         logging.Handler.__init__(self)
@@ -260,9 +321,25 @@ def emit(self, record):
         self.log_sink.log(msg)
 
 
+class LambdaLoggerHandlerWithFrameType(logging.Handler):
+    def __init__(self, log_sink):
+        super().__init__()
+        self.log_sink = log_sink
+
+    def emit(self, record):
+        self.log_sink.log(
+            self.format(record),
+            frame_type=(
+                getattr(record, "_frame_type", None)
+                or _TEXT_FRAME_TYPES.get(_format_log_level(record))
+            ),
+        )
+
+
 class LambdaLoggerFilter(logging.Filter):
     def filter(self, record):
         record.aws_request_id = _GLOBAL_AWS_REQUEST_ID or ""
+        record.tenant_id = _GLOBAL_TENANT_ID
         return True
 
 
@@ -298,7 +375,7 @@ def __enter__(self):
     def __exit__(self, exc_type, exc_value, exc_tb):
         pass
 
-    def log(self, msg):
+    def log(self, msg, frame_type=None):
         sys.stdout.write(msg)
 
     def log_error(self, message_lines):
@@ -312,35 +389,44 @@ class FramedTelemetryLogSink(object):
     framing protocol so message boundaries can be determined. Each frame can be visualized as follows:
      <pre>
     {@code
-    +----------------------+------------------------+-----------------------+
-    | Frame Type - 4 bytes | Length (len) - 4 bytes | Message - 'len' bytes |
-    +----------------------+------------------------+-----------------------+
+    +----------------------+------------------------+---------------------+-----------------------+
+    | Frame Type - 4 bytes | Length (len) - 4 bytes | Timestamp - 8 bytes | Message - 'len' bytes |
+    +----------------------+------------------------+---------------------+-----------------------+
     }
     </pre>
-    The first 4 bytes indicate the type of the frame - log frames have a type defined as the hex value 0xa55a0001. The
-    second 4 bytes should indicate the message's length. The next 'len' bytes contain the message. The byte order is
-    big-endian.
+    The first 4 bytes indicate the type of the frame - log frames have a type defined as the hex value 0xa55a0003. The
+    second 4 bytes should indicate the message's length. The next 8 bytes should indicate the timestamp of the message.
+    The next 'len' bytes contain the message. The byte order is big-endian.
     """
 
-    def __init__(self, filename):
-        self.filename = filename
-        self.frame_type = 0xA55A0001 .to_bytes(4, "big")
+    def __init__(self, fd):
+        self.fd = int(fd)
 
     def __enter__(self):
-        self.file = open(self.filename, "wb", 0)
+        self.file = os.fdopen(self.fd, "wb", 0)
         return self
 
     def __exit__(self, exc_type, exc_value, exc_tb):
         self.file.close()
 
-    def log(self, msg):
+    def log(self, msg, frame_type=None):
         encoded_msg = msg.encode("utf8")
-        log_msg = self.frame_type + len(encoded_msg).to_bytes(4, "big") + encoded_msg
+
+        timestamp = int(time.time_ns() / 1000)  # UNIX timestamp in microseconds
+        log_msg = (
+            (frame_type or _DEFAULT_FRAME_TYPE)
+            + len(encoded_msg).to_bytes(4, "big")
+            + timestamp.to_bytes(8, "big")
+            + encoded_msg
+        )
         self.file.write(log_msg)
 
     def log_error(self, message_lines):
         error_message = "\n".join(message_lines)
-        self.log(error_message)
+        self.log(
+            error_message,
+            frame_type=_ERROR_FRAME_TYPE,
+        )
 
 
 def update_xray_env_variable(xray_trace_id):
@@ -355,50 +441,88 @@ def create_log_sink():
     if "_LAMBDA_TELEMETRY_LOG_FD" in os.environ:
         fd = os.environ["_LAMBDA_TELEMETRY_LOG_FD"]
         del os.environ["_LAMBDA_TELEMETRY_LOG_FD"]
-        return FramedTelemetryLogSink("/proc/self/fd/" + fd)
+        return FramedTelemetryLogSink(fd)
 
     else:
         return StandardLogSink()
 
 
 _GLOBAL_AWS_REQUEST_ID = None
+_GLOBAL_TENANT_ID = None
+
+
+def _setup_logging(log_format, log_level, log_sink):
+    logging.Formatter.converter = time.gmtime
+    logger = logging.getLogger()
+
+    if log_format == LogFormat.JSON or log_level:
+        logger_handler = LambdaLoggerHandlerWithFrameType(log_sink)
+    else:
+        logger_handler = LambdaLoggerHandler(log_sink)
+
+    if log_format == LogFormat.JSON:
+        logger_handler.setFormatter(JsonFormatter())
+    else:
+        logger_handler.setFormatter(
+            logging.Formatter(
+                "[%(levelname)s]\t%(asctime)s.%(msecs)03dZ\t%(aws_request_id)s\t%(message)s\n",
+                "%Y-%m-%dT%H:%M:%S",
+            )
+        )
+
+    if log_level in logging._nameToLevel:
+        logger.setLevel(log_level)
+
+    logger_handler.addFilter(LambdaLoggerFilter())
+    logger.addHandler(logger_handler)
 
 
 def run(app_root, handler, lambda_runtime_api_addr):
     sys.stdout = Unbuffered(sys.stdout)
     sys.stderr = Unbuffered(sys.stderr)
 
+    use_thread_for_polling_next = os.environ.get("AWS_EXECUTION_ENV") in {
+        "AWS_Lambda_python3.12",
+        "AWS_Lambda_python3.13",
+    }
+
     with create_log_sink() as log_sink:
-        lambda_runtime_client = LambdaRuntimeClient(lambda_runtime_api_addr)
+        lambda_runtime_client = LambdaRuntimeClient(
+            lambda_runtime_api_addr, use_thread_for_polling_next
+        )
+        error_result = None
 
         try:
-            logging.Formatter.converter = time.gmtime
-            logger = logging.getLogger()
-            logger_handler = LambdaLoggerHandler(log_sink)
-            logger_handler.setFormatter(
-                logging.Formatter(
-                    "[%(levelname)s]\t%(asctime)s.%(msecs)dZ\t%(aws_request_id)s\t%(message)s\n",
-                    "%Y-%m-%dT%H:%M:%S",
-                )
-            )
-            logger_handler.addFilter(LambdaLoggerFilter())
-            logger.addHandler(logger_handler)
-
-            global _GLOBAL_AWS_REQUEST_ID
+            _setup_logging(_AWS_LAMBDA_LOG_FORMAT, _AWS_LAMBDA_LOG_LEVEL, log_sink)
+            global _GLOBAL_AWS_REQUEST_ID, _GLOBAL_TENANT_ID
 
             request_handler = _get_handler(handler)
+        except FaultException as e:
+            error_result = make_error(
+                e.msg,
+                e.exception_type,
+                e.trace,
+            )
         except Exception:
             error_result = build_fault_result(sys.exc_info(), None)
 
+        if error_result is not None:
+            from .lambda_literals import lambda_unhandled_exception_warning_message
+
+            logging.warning(lambda_unhandled_exception_warning_message)
             log_error(error_result, log_sink)
-            lambda_runtime_client.post_init_error(to_json(error_result))
+            lambda_runtime_client.post_init_error(error_result)
 
             sys.exit(1)
 
+        if os.environ.get(AWS_LAMBDA_INITIALIZATION_TYPE) == INIT_TYPE_SNAP_START:
+            on_init_complete(lambda_runtime_client, log_sink)
+
         while True:
             event_request = lambda_runtime_client.wait_next_invocation()
 
             _GLOBAL_AWS_REQUEST_ID = event_request.invoke_id
+            _GLOBAL_TENANT_ID = event_request.tenant_id
 
             update_xray_env_variable(event_request.x_amzn_trace_id)
 
@@ -412,5 +536,6 @@ def run(app_root, handler, lambda_runtime_api_addr):
                 event_request.cognito_identity,
                 event_request.invoked_function_arn,
                 event_request.deadline_time_in_ms,
+                event_request.tenant_id,
                 log_sink,
             )
diff --git a/awslambdaric/lambda_context.py b/awslambdaric/lambda_context.py
index 1465827..e0a3363 100644
--- a/awslambdaric/lambda_context.py
+++ b/awslambdaric/lambda_context.py
@@ -16,6 +16,7 @@ def __init__(
         cognito_identity,
         epoch_deadline_time_in_ms,
         invoked_function_arn=None,
+        tenant_id=None,
     ):
         self.aws_request_id = invoke_id
         self.log_group_name = os.environ.get("AWS_LAMBDA_LOG_GROUP_NAME")
@@ -24,6 +25,7 @@ def __init__(
         self.memory_limit_in_mb = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
         self.function_version = os.environ.get("AWS_LAMBDA_FUNCTION_VERSION")
         self.invoked_function_arn = invoked_function_arn
+        self.tenant_id = tenant_id
 
         self.client_context = make_obj_from_dict(ClientContext, client_context)
         if self.client_context is not None:
@@ -65,7 +67,8 @@ def __repr__(self):
             f"function_version={self.function_version},"
             f"invoked_function_arn={self.invoked_function_arn},"
             f"client_context={self.client_context},"
-            f"identity={self.identity}"
+            f"identity={self.identity},"
+            f"tenant_id={self.tenant_id}"
             "])"
         )
 
diff --git a/awslambdaric/lambda_literals.py b/awslambdaric/lambda_literals.py
new file mode 100644
index 0000000..2585b89
--- /dev/null
+++ b/awslambdaric/lambda_literals.py
@@ -0,0 +1,17 @@
+"""
+Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+"""
+
+lambda_warning = "LAMBDA_WARNING"
+
+# Holds warning message that is emitted when an unhandled exception is raised during function invocation.
+lambda_unhandled_exception_warning_message = str(
+    f"{lambda_warning}: "
+    "Unhandled exception. "
+    "The most likely cause is an issue in the function code. "
+    "However, in rare cases, a Lambda runtime update can cause unexpected function behavior. "
+    "For functions using managed runtimes, runtime updates can be triggered by a function change, or can be applied automatically. "
+    "To determine if the runtime has been updated, check the runtime version in the INIT_START log entry. "
+    "If this error correlates with a change in the runtime version, you may be able to mitigate this error by temporarily rolling back to the previous runtime version. "
+    "For more information, see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html\r"
+)
diff --git a/awslambdaric/lambda_runtime_client.py b/awslambdaric/lambda_runtime_client.py
index 70ff205..ba4ad92 100644
--- a/awslambdaric/lambda_runtime_client.py
+++ b/awslambdaric/lambda_runtime_client.py
@@ -2,25 +2,19 @@
 Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 """
 
-import http
-import http.client
 import sys
+from awslambdaric import __version__
+from .lambda_runtime_exception import FaultException
+from .lambda_runtime_marshaller import to_json
 
-try:
-    from importlib import metadata
-except ImportError:
-    # Running on pre-3.8 Python; use importlib-metadata package
-    import importlib_metadata as metadata
+ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"
 
 
 def _user_agent():
     py_version = (
         f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
     )
-    try:
-        pkg_version = metadata.version("awslambdaric")
-    except:
-        pkg_version = "unknown"
+    pkg_version = __version__
     return f"aws-lambda-python/{py_version}-{pkg_version}"
 
 
@@ -58,22 +52,84 @@ class LambdaRuntimeClient(object):
     and response. It allows for function authors to override the the default implementation, LambdaMarshaller which
     unmarshals and marshals JSON, to an instance of a class that implements the same interface."""
 
-    def __init__(self, lambda_runtime_address):
+    def __init__(self, lambda_runtime_address, use_thread_for_polling_next=False):
         self.lambda_runtime_address = lambda_runtime_address
+        self.use_thread_for_polling_next = use_thread_for_polling_next
+        if self.use_thread_for_polling_next:
+            # Conditionally import only for the case when TPE is used in this class.
+            from concurrent.futures import ThreadPoolExecutor
+
+            # Not defining symbol as global to avoid relying on TPE being imported unconditionally.
+            self.ThreadPoolExecutor = ThreadPoolExecutor
+
+    def call_rapid(
+        self, http_method, endpoint, expected_http_code, payload=None, headers=None
+    ):
+        # These imports are heavy-weight. They implicitly trigger `import ssl, hashlib`.
+        # Importing them lazily to speed up critical path of a common case.
+        import http.client
 
-    def post_init_error(self, error_response_data):
         runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address)
         runtime_connection.connect()
-        endpoint = "/2018-06-01/runtime/init/error"
-        runtime_connection.request("POST", endpoint, error_response_data)
+        if http_method == "GET":
+            runtime_connection.request(http_method, endpoint)
+        else:
+            runtime_connection.request(
+                http_method, endpoint, to_json(payload), headers=headers
+            )
+
         response = runtime_connection.getresponse()
         response_body = response.read()
-
-        if response.code != http.HTTPStatus.ACCEPTED:
+        if response.code != expected_http_code:
             raise LambdaRuntimeClientError(endpoint, response.code, response_body)
 
+    def post_init_error(self, error_response_data, error_type_override=None):
+        import http
+
+        endpoint = "/2018-06-01/runtime/init/error"
+        headers = {
+            ERROR_TYPE_HEADER: (
+                error_type_override
+                if error_type_override
+                else error_response_data["errorType"]
+            )
+        }
+        self.call_rapid(
+            "POST", endpoint, http.HTTPStatus.ACCEPTED, error_response_data, headers
+        )
+
+    def restore_next(self):
+        import http
+
+        endpoint = "/2018-06-01/runtime/restore/next"
+        self.call_rapid("GET", endpoint, http.HTTPStatus.OK)
+
+    def report_restore_error(self, restore_error_data):
+        import http
+
+        endpoint = "/2018-06-01/runtime/restore/error"
+        headers = {ERROR_TYPE_HEADER: FaultException.AFTER_RESTORE_ERROR}
+        self.call_rapid(
+            "POST", endpoint, http.HTTPStatus.ACCEPTED, restore_error_data, headers
+        )
+
     def wait_next_invocation(self):
-        response_body, headers = runtime_client.next()
+        # Calling runtime_client.next() from a separate thread unblocks the main thread,
+        # which can then process signals.
+        if self.use_thread_for_polling_next:
+            try:
+                # TPE class is supposed to be registered at construction time and be ready to use.
+                with self.ThreadPoolExecutor(max_workers=1) as executor:
+                    future = executor.submit(runtime_client.next)
+                response_body, headers = future.result()
+            except Exception as e:
+                raise FaultException(
+                    FaultException.LAMBDA_RUNTIME_CLIENT_ERROR,
+                    "LAMBDA_RUNTIME Failed to get next invocation: {}".format(str(e)),
+                    None,
+                )
+        else:
+            response_body, headers = runtime_client.next()
         return InvocationRequest(
             invoke_id=headers.get("Lambda-Runtime-Aws-Request-Id"),
             x_amzn_trace_id=headers.get("Lambda-Runtime-Trace-Id"),
@@ -81,6 +137,7 @@ def wait_next_invocation(self):
             deadline_time_in_ms=headers.get("Lambda-Runtime-Deadline-Ms"),
             client_context=headers.get("Lambda-Runtime-Client-Context"),
             cognito_identity=headers.get("Lambda-Runtime-Cognito-Identity"),
+            tenant_id=headers.get("Lambda-Runtime-Aws-Tenant-Id"),
             content_type=headers.get("Content-Type"),
             event_body=response_body,
         )
@@ -90,9 +147,11 @@ def post_invocation_result(
     ):
         runtime_client.post_invocation_result(
             invoke_id,
-            result_data
-            if isinstance(result_data, bytes)
-            else result_data.encode("utf-8"),
+            (
+                result_data
+                if isinstance(result_data, bytes)
+                else result_data.encode("utf-8")
+            ),
             content_type,
         )
 
diff --git a/awslambdaric/lambda_runtime_exception.py b/awslambdaric/lambda_runtime_exception.py
index 416327e..3ea5b29 100644
--- a/awslambdaric/lambda_runtime_exception.py
+++ b/awslambdaric/lambda_runtime_exception.py
@@ -11,7 +11,10 @@ class FaultException(Exception):
     IMPORT_MODULE_ERROR = "Runtime.ImportModuleError"
     BUILT_IN_MODULE_CONFLICT = "Runtime.BuiltInModuleConflict"
     MALFORMED_HANDLER_NAME = "Runtime.MalformedHandlerName"
+    BEFORE_SNAPSHOT_ERROR = "Runtime.BeforeSnapshotError"
+    AFTER_RESTORE_ERROR = "Runtime.AfterRestoreError"
     LAMBDA_CONTEXT_UNMARSHAL_ERROR = "Runtime.LambdaContextUnmarshalError"
+    LAMBDA_RUNTIME_CLIENT_ERROR = "Runtime.LambdaRuntimeClientError"
 
     def __init__(self, exception_type, msg, trace=None):
         self.msg = msg
diff --git a/awslambdaric/lambda_runtime_hooks_runner.py b/awslambdaric/lambda_runtime_hooks_runner.py
new file mode 100644
index 0000000..8aee181
--- /dev/null
+++ b/awslambdaric/lambda_runtime_hooks_runner.py
@@ -0,0 +1,18 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+from snapshot_restore_py import get_before_snapshot, get_after_restore
+
+
+def run_before_snapshot():
+    before_snapshot_callables = get_before_snapshot()
+    while before_snapshot_callables:
+        # Using pop as before checkpoint callables are executed in the reverse order of their registration
+        func, args, kwargs = before_snapshot_callables.pop()
+        func(*args, **kwargs)
+
+
+def run_after_restore():
+    after_restore_callables = get_after_restore()
+    for func, args, kwargs in after_restore_callables:
+        func(*args, **kwargs)
diff --git a/awslambdaric/lambda_runtime_log_utils.py b/awslambdaric/lambda_runtime_log_utils.py
new file mode 100644
index 0000000..9ddbcfb
--- /dev/null
+++ b/awslambdaric/lambda_runtime_log_utils.py
@@ -0,0 +1,139 @@
+"""
+Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+"""
+
+import json
+import logging
+import traceback
+from enum import IntEnum
+
+_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
+_RESERVED_FIELDS = {
+    "name",
+    "msg",
+    "args",
+    "levelname",
+    "levelno",
+    "pathname",
+    "filename",
+    "module",
+    "exc_info",
+    "exc_text",
+    "stack_info",
+    "lineno",
+    "funcName",
+    "created",
+    "msecs",
+    "relativeCreated",
+    "thread",
+    "threadName",
+    "processName",
+    "process",
+    "aws_request_id",
+    "tenant_id",
+    "_frame_type",
+}
+
+
+class LogFormat(IntEnum):
+    JSON = 0b0
+    TEXT = 0b1
+
+    @classmethod
+    def from_str(cls, value: str):
+        if value and value.upper() == "JSON":
+            return cls.JSON.value
+        return cls.TEXT.value
+
+
+def _get_log_level_from_env_var(log_level):
+    return {None: "", "TRACE": "DEBUG"}.get(log_level, log_level).upper()
+
+
+_JSON_FRAME_TYPES = {
+    logging.NOTSET: 0xA55A0002.to_bytes(4, "big"),
+    logging.DEBUG: 0xA55A000A.to_bytes(4, "big"),
+    logging.INFO: 0xA55A000E.to_bytes(4, "big"),
+    logging.WARNING: 0xA55A0012.to_bytes(4, "big"),
+    logging.ERROR: 0xA55A0016.to_bytes(4, "big"),
+    logging.CRITICAL: 0xA55A001A.to_bytes(4, "big"),
+}
+_TEXT_FRAME_TYPES = {
+    logging.NOTSET: 0xA55A0003.to_bytes(4, "big"),
+    logging.DEBUG: 0xA55A000B.to_bytes(4, "big"),
+    logging.INFO: 0xA55A000F.to_bytes(4, "big"),
+    logging.WARNING: 0xA55A0013.to_bytes(4, "big"),
+    logging.ERROR: 0xA55A0017.to_bytes(4, "big"),
+    logging.CRITICAL: 0xA55A001B.to_bytes(4, "big"),
+}
+_DEFAULT_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.NOTSET]
+
+_json_encoder = json.JSONEncoder(ensure_ascii=False)
+_encode_json = _json_encoder.encode
+
+
+def _format_log_level(record: logging.LogRecord) -> int:
+    return min(50, max(0, record.levelno)) // 10 * 10
+
+
+class JsonFormatter(logging.Formatter):
+    def __init__(self):
+        super().__init__(datefmt=_DATETIME_FORMAT)
+
+    @staticmethod
+    def __format_stacktrace(exc_info):
+        if not exc_info:
+            return None
+        return traceback.format_tb(exc_info[2])
+
+    @staticmethod
+    def __format_exception_name(exc_info):
+        if not exc_info:
+            return None
+
+        return exc_info[0].__name__
+
+    @staticmethod
+    def __format_exception(exc_info):
+        if not exc_info:
+            return None
+
+        return str(exc_info[1])
+
+    @staticmethod
+    def __format_location(record: logging.LogRecord):
+        if not record.exc_info:
+            return None
+
+        return f"{record.pathname}:{record.funcName}:{record.lineno}"
+
+    def format(self, record: logging.LogRecord) -> str:
+        record.levelno = _format_log_level(record)
+        record.levelname = logging.getLevelName(record.levelno)
+        record._frame_type = _JSON_FRAME_TYPES.get(
+            record.levelno, _JSON_FRAME_TYPES[logging.NOTSET]
+        )
+
+        result = {
+            "timestamp": self.formatTime(record, self.datefmt),
+            "level": record.levelname,
+            "message": record.getMessage(),
+            "logger": record.name,
+            "stackTrace": self.__format_stacktrace(record.exc_info),
+            "errorType": self.__format_exception_name(record.exc_info),
+            "errorMessage": self.__format_exception(record.exc_info),
+            "requestId": getattr(record, "aws_request_id", None),
+            "location": self.__format_location(record),
+        }
+        if hasattr(record, "tenant_id") and record.tenant_id is not None:
+            result["tenantId"] = record.tenant_id
+
+        result.update(
+            (key, value)
+            for key, value in record.__dict__.items()
+            if key not in _RESERVED_FIELDS and key not in result
+        )
+
+        result = {k: v for k, v in result.items() if v is not None}
+
+        return _encode_json(result) + "\n"
diff --git a/awslambdaric/lambda_runtime_marshaller.py b/awslambdaric/lambda_runtime_marshaller.py
index 7eee25d..4256066 100644
--- a/awslambdaric/lambda_runtime_marshaller.py
+++ b/awslambdaric/lambda_runtime_marshaller.py
@@ -4,7 +4,7 @@
 
 import decimal
 import math
-
+import os
 import simplejson as json
 
 from .lambda_runtime_exception import FaultException
@@ -12,9 +12,16 @@
 
 # simplejson's Decimal encoding allows '-NaN' as an output, which is a parse error for json.loads
 # to get the good parts of Decimal support, we'll special-case NaN decimals and otherwise duplicate the encoding for decimals the same way simplejson does
+# We also set 'ensure_ascii=False' so that the encoded json contains unicode characters instead of unicode escape sequences
 class Encoder(json.JSONEncoder):
     def __init__(self):
-        super().__init__(use_decimal=False)
+        if os.environ.get("AWS_EXECUTION_ENV") in {
+            "AWS_Lambda_python3.12",
+            "AWS_Lambda_python3.13",
+        }:
+            super().__init__(use_decimal=False, ensure_ascii=False, allow_nan=True)
+        else:
+            super().__init__(use_decimal=False, allow_nan=True)
 
     def default(self, obj):
         if isinstance(obj, decimal.Decimal):
diff --git a/awslambdaric/runtime_client.cpp b/awslambdaric/runtime_client.cpp
index 66252bf..7fb2e95 100644
--- a/awslambdaric/runtime_client.cpp
+++ b/awslambdaric/runtime_client.cpp
@@ -52,9 +52,10 @@ static PyObject *method_next(PyObject *self) {
     auto client_context = response.client_context.c_str();
     auto content_type = response.content_type.c_str();
     auto cognito_id = response.cognito_identity.c_str();
+    auto tenant_id = response.tenant_id.c_str();
 
     PyObject *payload_bytes = PyBytes_FromStringAndSize(payload.c_str(), payload.length());
-    PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s})",
+    PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s,s:s})",
                          payload_bytes,  //Py_BuildValue() increments reference counter
                          "Lambda-Runtime-Aws-Request-Id", request_id,
                          "Lambda-Runtime-Trace-Id", NULL_IF_EMPTY(trace_id),
@@ -62,7 +63,8 @@ static PyObject *method_next(PyObject *self) {
                          "Lambda-Runtime-Deadline-Ms", deadline,
                          "Lambda-Runtime-Client-Context", NULL_IF_EMPTY(client_context),
                          "Content-Type", NULL_IF_EMPTY(content_type),
-                         "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id)
+                         "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id),
+                         "Lambda-Runtime-Aws-Tenant-Id", NULL_IF_EMPTY(tenant_id)
     );
 
     Py_XDECREF(payload_bytes);
diff --git a/deps/aws-lambda-cpp-0.2.6.tar.gz b/deps/aws-lambda-cpp-0.2.6.tar.gz
index 26fa498..51d7f51 100644
Binary files a/deps/aws-lambda-cpp-0.2.6.tar.gz and b/deps/aws-lambda-cpp-0.2.6.tar.gz differ
diff --git a/deps/curl-7.77.0.tar.gz b/deps/curl-7.77.0.tar.gz
deleted file mode 100644
index 951f34b..0000000
Binary files a/deps/curl-7.77.0.tar.gz and /dev/null differ
diff --git a/deps/curl-7.83.1.tar.gz b/deps/curl-7.83.1.tar.gz
new file mode 100644
index 0000000..305bb4f
Binary files /dev/null and b/deps/curl-7.83.1.tar.gz differ
diff --git a/deps/patches/aws-lambda-cpp-add-tenant-id.patch b/deps/patches/aws-lambda-cpp-add-tenant-id.patch
new file mode 100644
index 0000000..a7b7172
--- /dev/null
+++ b/deps/patches/aws-lambda-cpp-add-tenant-id.patch
@@ -0,0 +1,39 @@
+diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h
+index 7812ff6..96be869 100644
+--- a/include/aws/lambda-runtime/runtime.h
++++ b/include/aws/lambda-runtime/runtime.h
+@@ -61,6 +61,11 @@ struct invocation_request {
+      */
+     std::string content_type;
+ 
++    /**
++     * The Tenant ID of the current invocation.
++     */
++    std::string tenant_id;
++
+     /**
+      * Function execution deadline counted in milliseconds since the Unix epoch.
+      */
+diff --git a/src/runtime.cpp b/src/runtime.cpp
+index e53b2b8..9763282 100644
+--- a/src/runtime.cpp
++++ b/src/runtime.cpp
+@@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context";
+ static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity";
+ static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms";
+ static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn";
++static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id";
+ 
+ enum Endpoints {
+     INIT,
+@@ -289,6 +290,10 @@ runtime::next_outcome runtime::get_next()
+         req.function_arn = resp.get_header(FUNCTION_ARN_HEADER);
+     }
+ 
++    if (resp.has_header(TENANT_ID_HEADER)) {
++        req.tenant_id = resp.get_header(TENANT_ID_HEADER);
++    }
++
+     if (resp.has_header(DEADLINE_MS_HEADER)) {
+         auto const& deadline_string = resp.get_header(DEADLINE_MS_HEADER);
+         constexpr int base = 10;
diff --git a/deps/patches/libcurl-configure-template.patch b/deps/patches/libcurl-configure-template.patch
new file mode 100644
index 0000000..e26be47
--- /dev/null
+++ b/deps/patches/libcurl-configure-template.patch
@@ -0,0 +1,131 @@
+diff --git a/configure.ac b/configure.ac
+index d24daea..64aca7f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -193,87 +193,96 @@ AS_HELP_STRING([--with-schannel],[enable Windows native SSL/TLS]),
+ 
+ OPT_SECURETRANSPORT=no
+ AC_ARG_WITH(secure-transport,dnl
+-AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]),
++AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]),[
+   OPT_SECURETRANSPORT=$withval
+   test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }Secure-Transport"
+-)
++])
+ 
+ OPT_AMISSL=no
+ AC_ARG_WITH(amissl,dnl
+-AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]),
++AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]),[
+   OPT_AMISSL=$withval
+-  test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL")
++  test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL"
++])
++
+ 
+ OPT_OPENSSL=no
+ dnl Default to no CA bundle
+ ca="no"
+ AC_ARG_WITH(ssl,dnl
+ AS_HELP_STRING([--with-ssl=PATH],[old version of --with-openssl])
+-AS_HELP_STRING([--without-ssl], [build without any TLS library]),
++AS_HELP_STRING([--without-ssl], [build without any TLS library]),[
+   OPT_SSL=$withval
+   OPT_OPENSSL=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL"
+   fi
++])
+ 
+ AC_ARG_WITH(openssl,dnl
+-AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]),
++AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]),[
+   OPT_OPENSSL=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL"
+   fi
++])
+ 
+ OPT_GNUTLS=no
+ AC_ARG_WITH(gnutls,dnl
+-AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]),
++AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]),[
+   OPT_GNUTLS=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS"
+   fi
++])
+ 
+ OPT_MBEDTLS=no
+ AC_ARG_WITH(mbedtls,dnl
+-AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]),
++AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]),[
+   OPT_MBEDTLS=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS"
+   fi
++])
+ 
+ OPT_WOLFSSL=no
+ AC_ARG_WITH(wolfssl,dnl
+-AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]),
++AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]),[
+   OPT_WOLFSSL=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL"
+   fi
++])
+ 
+ OPT_BEARSSL=no
+ AC_ARG_WITH(bearssl,dnl
+-AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]),
++AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]),[
+   OPT_BEARSSL=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL"
+   fi
++])
+ 
+ OPT_RUSTLS=no
+ AC_ARG_WITH(rustls,dnl
+-AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]),
++AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]),[
+   OPT_RUSTLS=$withval
+   if test X"$withval" != Xno; then
+-    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls")
++    test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls"
+   fi
++])
+ 
+ OPT_NSS_AWARE=no
+ AC_ARG_WITH(nss-deprecated,dnl
+-AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]),
++AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]),[
+   if test X"$withval" != Xno; then
+     OPT_NSS_AWARE=$withval
+   fi
+-)
++])
+ 
+ OPT_NSS=no
+ AC_ARG_WITH(nss,dnl
+-AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]),
++AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]),[
+   OPT_NSS=$withval
+   if test X"$withval" != Xno; then
+ 
+@@ -283,7 +292,7 @@ AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the inst
+ 
+     test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }NSS"
+   fi
+-)
++])
+ 
+ dnl If no TLS choice has been made, check if it was explicitly disabled or
+ dnl error out to force the user to decide.
diff --git a/deps/versions b/deps/versions
index d8c5b10..63b0efc 100644
--- a/deps/versions
+++ b/deps/versions
@@ -1,2 +1,4 @@
 AWS_LAMBDA_CPP_RELEASE=0.2.6
-CURL_VERSION=7.77.0
\ No newline at end of file
+CURL_MAJOR_VERSION=7
+CURL_MINOR_VERSION=83
+CURL_PATCH_VERSION=1
diff --git a/requirements/base.txt b/requirements/base.txt
index eb0e59b..4bb251e 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,2 +1,2 @@
-simplejson==3.17.2
-importlib-metadata ~= 2.0 ; python_version < "3.8"
+simplejson>=3.20.1
+snapshot-restore-py>=1.0.0
diff --git a/requirements/dev.txt b/requirements/dev.txt
index c432413..68377ce 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -9,3 +9,4 @@ bandit>=1.6.2
 # Test requirements
 pytest>=3.0.7
 mock>=2.0.0
+parameterized>=0.9.0
\ No newline at end of file
diff --git a/scripts/preinstall.sh b/scripts/preinstall.sh
index 5db0c46..b377228 100755
--- a/scripts/preinstall.sh
+++ b/scripts/preinstall.sh
@@ -19,12 +19,14 @@ else
     cd deps
     . ./versions
 
+    CURL_VERSION="${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}"
+
     rm -rf ./curl-$CURL_VERSION
     rm -rf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE
 
     # unpack dependencies
-    tar xzf ./curl-$CURL_VERSION.tar.gz && \
-    tar xzf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz
+    tar xzf ./curl-$CURL_VERSION.tar.gz --no-same-owner && \
+    tar xzf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz --no-same-owner
 
     (
         # Build Curl
@@ -34,6 +36,7 @@ else
                 --prefix "$ARTIFACTS_DIR" \
                 --disable-shared \
                 --without-ssl \
+                --with-pic \
                 --without-zlib && \
             make && \
             make install
diff --git a/scripts/update_deps.sh b/scripts/update_deps.sh
index db34a11..4799a6f 100755
--- a/scripts/update_deps.sh
+++ b/scripts/update_deps.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-set -e
+set -x
 
 cd deps
 source versions
@@ -8,8 +8,17 @@ source versions
 # Clean up old files
 rm -f aws-lambda-cpp-*.tar.gz && rm -f curl-*.tar.gz
 
+
+LIBCURL="curl-${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}"
+
 # Grab Curl
-wget -c https://github.com/curl/curl/archive/curl-$CURL_VERSION.tar.gz
+wget -c "https://github.com/curl/curl/releases/download/curl-${CURL_MAJOR_VERSION}_${CURL_MINOR_VERSION}_${CURL_PATCH_VERSION}/$LIBCURL.tar.gz" -O - | tar -xz
+(
+  cd  $LIBCURL && \
+  patch -p1 < ../patches/libcurl-configure-template.patch
+)
+
+tar -czf $LIBCURL.tar.gz $LIBCURL --no-same-owner && rm -rf $LIBCURL
 
 # Grab aws-lambda-cpp
 wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEASE.tar.gz -O - | tar -xz
@@ -21,9 +30,10 @@ wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEA
     patch -p1 < ../patches/aws-lambda-cpp-posting-init-errors.patch && \
     patch -p1 < ../patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch && \
     patch -p1 < ../patches/aws-lambda-cpp-make-lto-optional.patch && \
-    patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch
+    patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch && \
+    patch -p1 < ../patches/aws-lambda-cpp-add-tenant-id.patch
 )
 
 ## Pack again and remove the folder
-tar -czvf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE && \
+tar -czf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE --no-same-owner && \
   rm -rf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE
diff --git a/setup.py b/setup.py
index 1d8a2ce..2bf28ef 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,7 @@
 import platform
 from subprocess import check_call, check_output
 from setuptools import Extension, find_packages, setup
+from awslambdaric import __version__
 
 
 def get_curl_extra_linker_flags():
@@ -68,7 +69,7 @@ def read_requirements(req="base.txt"):
 
 setup(
     name="awslambdaric",
-    version="1.2.0",
+    version=__version__,
     author="Amazon Web Services",
     description="AWS Lambda Runtime Interface Client for Python",
     long_description=read("README.md"),
@@ -83,14 +84,15 @@ def read_requirements(req="base.txt"):
         "Intended Audience :: Developers",
         "Natural Language :: English",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
-        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+        "Programming Language :: Python :: 3.12",
+        "Programming Language :: Python :: 3.13",
         "License :: OSI Approved :: Apache Software License",
         "Operating System :: OS Independent",
     ],
-    python_requires=">=3.6",
+    python_requires=">=3.9",
     ext_modules=get_runtime_client_extension(),
     test_suite="tests",
 )
diff --git a/tests/integration/codebuild-local/Dockerfile.agent b/tests/integration/codebuild-local/Dockerfile.agent
index e9a9ffe..1016c0c 100644
--- a/tests/integration/codebuild-local/Dockerfile.agent
+++ b/tests/integration/codebuild-local/Dockerfile.agent
@@ -1,4 +1,4 @@
-FROM amazonlinux:2
+FROM public.ecr.aws/amazonlinux/amazonlinux:2
 
 RUN amazon-linux-extras enable docker && \
     yum clean metadata && \
diff --git a/tests/integration/codebuild-local/codebuild_build.sh b/tests/integration/codebuild-local/codebuild_build.sh
index ffadfa3..45329d2 100755
--- a/tests/integration/codebuild-local/codebuild_build.sh
+++ b/tests/integration/codebuild-local/codebuild_build.sh
@@ -36,6 +36,7 @@ function usage {
     echo "  -a        Used to specify an artifact output directory."
     echo "Options:"
     echo "  -l IMAGE  Used to override the default local agent image."
+    echo "  -r        Used to specify a report output directory."
     echo "  -s        Used to specify source information. Defaults to the current working directory for primary source."
     echo "               * First (-s) is for primary source"
     echo "               * Use additional (-s) in <sourceIdentifier>:<sourceLocation> format for secondary source"
@@ -61,10 +62,11 @@ awsconfig_flag=false
 mount_src_dir_flag=false
 docker_privileged_mode_flag=false
 
-while getopts "cmdi:a:s:b:e:l:p:h" opt; do
+while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do
     case $opt in
         i  ) image_flag=true; image_name=$OPTARG;;
         a  ) artifact_flag=true; artifact_dir=$OPTARG;;
+        r  ) report_dir=$OPTARG;;
         b  ) buildspec=$OPTARG;;
         c  ) awsconfig_flag=true;;
         m  ) mount_src_dir_flag=true;;
@@ -106,6 +108,11 @@ fi
 docker_command+="\"IMAGE_NAME=$image_name\" -e \
     \"ARTIFACTS=$(allOSRealPath "$artifact_dir")\""
 
+if [ -n "$report_dir" ]
+then
+    docker_command+=" -e \"REPORTS=$(allOSRealPath "$report_dir")\""
+fi
+
 if [ -z "$source_dirs" ]
 then
     docker_command+=" -e \"SOURCE=$(allOSRealPath "$PWD")\""
@@ -136,11 +143,6 @@ then
     docker_command+=" -v \"$environment_variable_file_dir:/LocalBuild/envFile/\" -e \"ENV_VAR_FILE=$environment_variable_file_basename\""
 fi
 
-if [ -n "$local_agent_image" ]
-then
-    docker_command+=" -e \"LOCAL_AGENT_IMAGE_NAME=$local_agent_image\""
-fi
-
 if $awsconfig_flag
 then
     if [ -d "$HOME/.aws" ]
@@ -176,7 +178,12 @@ else
     docker_command+=" -e \"INITIATOR=$USER\""
 fi
 
-docker_command+=" amazon/aws-codebuild-local:latest"
+if [ -n "$local_agent_image" ]
+then
+    docker_command+=" $local_agent_image"
+else
+    docker_command+=" public.ecr.aws/codebuild/local-builds:latest"
+fi
 
 # Note we do not expose the AWS_SECRET_ACCESS_KEY or the AWS_SESSION_TOKEN
 exposed_command=$docker_command
@@ -191,4 +198,4 @@ echo ""
 echo $exposed_command
 echo ""
 
-eval $docker_command
+eval $docker_command
\ No newline at end of file
diff --git a/tests/integration/codebuild-local/test_all.sh b/tests/integration/codebuild-local/test_all.sh
index 0c5168c..1a09241 100755
--- a/tests/integration/codebuild-local/test_all.sh
+++ b/tests/integration/codebuild-local/test_all.sh
@@ -5,6 +5,7 @@ set -euo pipefail
 
 CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}"
 DRYRUN="${DRYRUN-0}"
+DISTRO="${DISTRO:=""}"
 
 function usage {
     echo "usage: test_all.sh buildspec_yml_dir"
@@ -51,10 +52,12 @@ main() {
         usage
         exit 1
     fi
-
+    
     BUILDSPEC_YML_DIR="$1"
+    echo $DISTRO $BUILDSPEC_YML_DIR
+    ls $BUILDSPEC_YML_DIR
     HAS_YML=0
-    for f in "$BUILDSPEC_YML_DIR"/*.yml ; do
+    for f in "$BUILDSPEC_YML_DIR"/*"$DISTRO"*.yml ; do
         [ -f "$f" ] || continue;
         do_one_yaml "$f"
         HAS_YML=1
diff --git a/tests/integration/codebuild/buildspec.os.alpine.1.yml b/tests/integration/codebuild/buildspec.os.alpine.1.yml
deleted file mode 100644
index bea310e..0000000
--- a/tests/integration/codebuild/buildspec.os.alpine.1.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-version: 0.2
-
-env:
-  variables:
-    OS_DISTRIBUTION: alpine
-    PYTHON_LOCATION: "/usr/local/bin/python"
-    TEST_NAME: "aws-lambda-python-rtc-alpine-1-test"
-batch:
-  build-matrix:
-    static:
-      ignore-failure: false
-      env:
-        type: LINUX_CONTAINER
-        privileged-mode: true
-    dynamic:
-      env:
-        variables:
-          DISTRO_VERSION:
-            - "3.9"
-          RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-phases:
-  pre_build:
-    commands:
-      - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}"
-      - echo "Extracting and including the Runtime Interface Emulator"
-      - SCRATCH_DIR=".scratch"
-      - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
-      - >
-        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        echo "RUN apk add curl" >> \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
-      - echo "Building image ${IMAGE_TAG}"
-      - >
-        docker build . \
-          -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-          -t "${IMAGE_TAG}" \
-          --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
-  build:
-    commands:
-      - set -x
-      - echo "Running Image ${IMAGE_TAG}"
-      - docker network create "${TEST_NAME}-network"
-      - >
-        docker run \
-          --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
-          --name "${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
-      - sleep 2
-      - >
-        docker run \
-          --name "${TEST_NAME}-tester" \
-          --env "TARGET=${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10'
-      - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)"
-      - expected='success'
-      - |
-        echo "Response: ${actual}"
-        if [[ "$actual" != "$expected" ]]; then
-          echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
-          exit -1
-        fi
-    finally:
-      - echo "Cleaning up..."
-      - docker stop "${TEST_NAME}-app" || true
-      - docker rm --force "${TEST_NAME}-app" || true
-      - docker stop "${TEST_NAME}-tester" || true
-      - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
diff --git a/tests/integration/codebuild/buildspec.os.alpine.2.yml b/tests/integration/codebuild/buildspec.os.alpine.yml
similarity index 65%
rename from tests/integration/codebuild/buildspec.os.alpine.2.yml
rename to tests/integration/codebuild/buildspec.os.alpine.yml
index 0222c98..8b290f5 100644
--- a/tests/integration/codebuild/buildspec.os.alpine.2.yml
+++ b/tests/integration/codebuild/buildspec.os.alpine.yml
@@ -4,24 +4,25 @@ env:
   variables:
     OS_DISTRIBUTION: alpine
     PYTHON_LOCATION: "/usr/local/bin/python"
-    TEST_NAME: "aws-lambda-python-rtc-alpine-2-test"
+    TEST_NAME: "aws-lambda-python-rtc-alpine-test"
 batch:
   build-matrix:
     static:
       ignore-failure: false
       env:
-        type: LINUX_CONTAINER
         privileged-mode: true
     dynamic:
       env:
         variables:
           DISTRO_VERSION:
+            - "3.19"
+            - "3.20"
+          RUNTIME_VERSION:
+            - "3.9"
             - "3.10"
             - "3.11"
-          RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
+            - "3.12"
+            - "3.13"
 phases:
   pre_build:
     commands:
@@ -29,7 +30,17 @@ phases:
       - echo "Extracting and including the Runtime Interface Emulator"
       - SCRATCH_DIR=".scratch"
       - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
+      - ARCHITECTURE=$(arch)
+      - >
+        if [[ "$ARCHITECTURE" == "x86_64" ]]; then
+            RIE="aws-lambda-rie"
+        elif [[ "$ARCHITECTURE" == "aarch64" ]]; then
+            RIE="aws-lambda-rie-arm64"
+        else
+            echo "Architecture $ARCHITECTURE is not currently supported."
+            exit 1
+        fi
+      - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}"
       - >
         cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
@@ -37,23 +48,19 @@ phases:
         echo "RUN apk add curl" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
+        echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
+        echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+        service docker restart
       - echo "Building image ${IMAGE_TAG}"
       - >
         docker build . \
           -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
           -t "${IMAGE_TAG}" \
           --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+          --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+          --load
   build:
     commands:
       - set -x
@@ -62,12 +69,11 @@ phases:
       - >
         docker run \
           --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
           --name "${TEST_NAME}-app" \
           --network "${TEST_NAME}-network" \
           --entrypoint="" \
           "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
+          sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler"
       - sleep 2
       - >
         docker run \
@@ -83,19 +89,20 @@ phases:
         echo "Response: ${actual}"
         if [[ "$actual" != "$expected" ]]; then
           echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
           exit -1
         fi
     finally:
+      - |
+        echo "---------Container Logs: ${TEST_NAME}-app----------"
+        echo
+        docker logs "${TEST_NAME}-app" || true
+        echo
+        echo "---------------------------------------------------"
+        echo "--------Container Logs: ${TEST_NAME}-tester--------"
+        echo
+        docker logs "${TEST_NAME}-tester" || true
+        echo
+        echo "---------------------------------------------------"
       - echo "Cleaning up..."
       - docker stop "${TEST_NAME}-app" || true
       - docker rm --force "${TEST_NAME}-app" || true
diff --git a/tests/integration/codebuild/buildspec.os.amazonlinux.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
similarity index 63%
rename from tests/integration/codebuild/buildspec.os.amazonlinux.yml
rename to tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
index a8e9f4c..05722bb 100644
--- a/tests/integration/codebuild/buildspec.os.amazonlinux.yml
+++ b/tests/integration/codebuild/buildspec.os.amazonlinux.2.yml
@@ -2,7 +2,7 @@ version: 0.2
 
 env:
   variables:
-    OS_DISTRIBUTION: amazonlinux
+    OS_DISTRIBUTION: amazonlinux2
     PYTHON_LOCATION: "/usr/local/bin/python3"
     TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test"
 batch:
@@ -10,19 +10,16 @@ batch:
     static:
       ignore-failure: false
       env:
-        type: LINUX_CONTAINER
         privileged-mode: true
     dynamic:
       env:
         variables:
           DISTRO_VERSION:
-            - "1"
             - "2"
           RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
             - "3.9"
+            - "3.10"
+            - "3.11"
 phases:
   pre_build:
     commands:
@@ -30,28 +27,35 @@ phases:
       - echo "Extracting and including the Runtime Interface Emulator"
       - SCRATCH_DIR=".scratch"
       - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
+      - ARCHITECTURE=$(arch)
+      - >
+        if [[ "$ARCHITECTURE" == "x86_64" ]]; then
+            RIE="aws-lambda-rie"
+        elif [[ "$ARCHITECTURE" == "aarch64" ]]; then
+            RIE="aws-lambda-rie-arm64"
+        else
+            echo "Architecture $ARCHITECTURE is not currently supported."
+            exit 1
+        fi
+      - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}"
       - >
         cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
+        echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
+        echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+        service docker restart
       - echo "Building image ${IMAGE_TAG}"
       - >
         docker build . \
           -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
           -t "${IMAGE_TAG}" \
           --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+          --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+          --build-arg ARCHITECTURE="${ARCHITECTURE}" \
+          --load
   build:
     commands:
       - set -x
@@ -60,12 +64,11 @@ phases:
       - >
         docker run \
           --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
           --name "${TEST_NAME}-app" \
           --network "${TEST_NAME}-network" \
           --entrypoint="" \
           "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
+          sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler"
       - sleep 2
       - >
         docker run \
@@ -81,22 +84,23 @@ phases:
         echo "Response: ${actual}"
         if [[ "$actual" != "$expected" ]]; then
           echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
           exit -1
         fi
     finally:
+      - |
+        echo "---------Container Logs: ${TEST_NAME}-app----------"
+        echo
+        docker logs "${TEST_NAME}-app" || true
+        echo
+        echo "---------------------------------------------------"
+        echo "--------Container Logs: ${TEST_NAME}-tester--------"
+        echo
+        docker logs "${TEST_NAME}-tester" || true
+        echo
+        echo "---------------------------------------------------"
       - echo "Cleaning up..."
       - docker stop "${TEST_NAME}-app" || true
       - docker rm --force "${TEST_NAME}-app" || true
       - docker stop "${TEST_NAME}-tester" || true
       - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
+      - docker network rm "${TEST_NAME}-network" || true
diff --git a/tests/integration/codebuild/buildspec.os.centos.1.yml b/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
similarity index 61%
rename from tests/integration/codebuild/buildspec.os.centos.1.yml
rename to tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
index 48760d6..9d6d20f 100644
--- a/tests/integration/codebuild/buildspec.os.centos.1.yml
+++ b/tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml
@@ -2,26 +2,23 @@ version: 0.2
 
 env:
   variables:
-    OS_DISTRIBUTION: centos
+    OS_DISTRIBUTION: amazonlinux2023
     PYTHON_LOCATION: "/usr/local/bin/python3"
-    TEST_NAME: "aws-lambda-python-rtc-centos-1-test"
+    TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test"
 batch:
   build-matrix:
     static:
       ignore-failure: false
       env:
-        type: LINUX_CONTAINER
         privileged-mode: true
     dynamic:
       env:
         variables:
           DISTRO_VERSION:
-            - "7"
+            - "2023"
           RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
-            - "3.9"
+            - "3.12"
+            - "3.13"
 phases:
   pre_build:
     commands:
@@ -29,28 +26,35 @@ phases:
       - echo "Extracting and including the Runtime Interface Emulator"
       - SCRATCH_DIR=".scratch"
       - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
+      - ARCHITECTURE=$(arch)
+      - >
+        if [[ "$ARCHITECTURE" == "x86_64" ]]; then
+            RIE="aws-lambda-rie"
+        elif [[ "$ARCHITECTURE" == "aarch64" ]]; then
+            RIE="aws-lambda-rie-arm64"
+        else
+            echo "Architecture $ARCHITECTURE is not currently supported."
+            exit 1
+        fi
+      - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}"
       - >
         cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
+        echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
+        echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+        service docker restart
       - echo "Building image ${IMAGE_TAG}"
       - >
         docker build . \
           -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
           -t "${IMAGE_TAG}" \
           --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+          --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+          --build-arg ARCHITECTURE="${ARCHITECTURE}" \
+          --load
   build:
     commands:
       - set -x
@@ -59,12 +63,11 @@ phases:
       - >
         docker run \
           --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
           --name "${TEST_NAME}-app" \
           --network "${TEST_NAME}-network" \
           --entrypoint="" \
           "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
+          sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler"
       - sleep 2
       - >
         docker run \
@@ -80,22 +83,23 @@ phases:
         echo "Response: ${actual}"
         if [[ "$actual" != "$expected" ]]; then
           echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
           exit -1
         fi
     finally:
+      - |
+        echo "---------Container Logs: ${TEST_NAME}-app----------"
+        echo
+        docker logs "${TEST_NAME}-app" || true
+        echo
+        echo "---------------------------------------------------"
+        echo "--------Container Logs: ${TEST_NAME}-tester--------"
+        echo
+        docker logs "${TEST_NAME}-tester" || true
+        echo
+        echo "---------------------------------------------------"
       - echo "Cleaning up..."
       - docker stop "${TEST_NAME}-app" || true
       - docker rm --force "${TEST_NAME}-app" || true
       - docker stop "${TEST_NAME}-tester" || true
       - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
+      - docker network rm "${TEST_NAME}-network" || true
diff --git a/tests/integration/codebuild/buildspec.os.centos.2.yml b/tests/integration/codebuild/buildspec.os.centos.2.yml
deleted file mode 100644
index a09bf70..0000000
--- a/tests/integration/codebuild/buildspec.os.centos.2.yml
+++ /dev/null
@@ -1,101 +0,0 @@
-version: 0.2
-
-env:
-  variables:
-    OS_DISTRIBUTION: centos
-    PYTHON_LOCATION: "/usr/local/bin/python3"
-    TEST_NAME: "aws-lambda-python-rtc-centos-2-test"
-batch:
-  build-matrix:
-    static:
-      ignore-failure: false
-      env:
-        type: LINUX_CONTAINER
-        privileged-mode: true
-    dynamic:
-      env:
-        variables:
-          DISTRO_VERSION:
-            - "8"
-          RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
-            - "3.9"
-phases:
-  pre_build:
-    commands:
-      - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}"
-      - echo "Extracting and including the Runtime Interface Emulator"
-      - SCRATCH_DIR=".scratch"
-      - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
-      - >
-        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
-      - echo "Building image ${IMAGE_TAG}"
-      - >
-        docker build . \
-          -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-          -t "${IMAGE_TAG}" \
-          --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
-  build:
-    commands:
-      - set -x
-      - echo "Running Image ${IMAGE_TAG}"
-      - docker network create "${TEST_NAME}-network"
-      - >
-        docker run \
-          --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
-          --name "${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
-      - sleep 2
-      - >
-        docker run \
-          --name "${TEST_NAME}-tester" \
-          --env "TARGET=${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10'
-      - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)"
-      - expected='success'
-      - |
-        echo "Response: ${actual}"
-        if [[ "$actual" != "$expected" ]]; then
-          echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
-          exit -1
-        fi
-    finally:
-      - echo "Cleaning up..."
-      - docker stop "${TEST_NAME}-app" || true
-      - docker rm --force "${TEST_NAME}-app" || true
-      - docker stop "${TEST_NAME}-tester" || true
-      - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
diff --git a/tests/integration/codebuild/buildspec.os.debian.yml b/tests/integration/codebuild/buildspec.os.debian.yml
index 227e994..44c061f 100644
--- a/tests/integration/codebuild/buildspec.os.debian.yml
+++ b/tests/integration/codebuild/buildspec.os.debian.yml
@@ -10,18 +10,19 @@ batch:
     static:
       ignore-failure: false
       env:
-        type: LINUX_CONTAINER
         privileged-mode: true
     dynamic:
       env:
         variables:
           DISTRO_VERSION:
-            - "buster"
+            - "bookworm"
+            - "bullseye"
           RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
             - "3.9"
+            - "3.10"
+            - "3.11"
+            - "3.12"
+            - "3.13"
 phases:
   pre_build:
     commands:
@@ -29,31 +30,37 @@ phases:
       - echo "Extracting and including the Runtime Interface Emulator"
       - SCRATCH_DIR=".scratch"
       - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
+      - ARCHITECTURE=$(arch)
+      - >
+        if [[ "$ARCHITECTURE" == "x86_64" ]]; then
+            RIE="aws-lambda-rie"
+        elif [[ "$ARCHITECTURE" == "aarch64" ]]; then
+            RIE="aws-lambda-rie-arm64"
+        else
+            echo "Architecture $ARCHITECTURE is not currently supported."
+            exit 1
+        fi
+      - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}"
       - >
         cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
+        echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
       - >
         echo "RUN apt-get update && apt-get install -y curl" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
+      - >
+        echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+        service docker restart
       - echo "Building image ${IMAGE_TAG}"
       - >
         docker build . \
           -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
           -t "${IMAGE_TAG}" \
           --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+          --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+          --load
   build:
     commands:
       - set -x
@@ -62,12 +69,11 @@ phases:
       - >
         docker run \
           --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
           --name "${TEST_NAME}-app" \
           --network "${TEST_NAME}-network" \
           --entrypoint="" \
           "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
+          sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler"
       - sleep 2
       - >
         docker run \
@@ -83,22 +89,23 @@ phases:
         echo "Response: ${actual}"
         if [[ "$actual" != "$expected" ]]; then
           echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
           exit -1
         fi
     finally:
+      - |
+        echo "---------Container Logs: ${TEST_NAME}-app----------"
+        echo
+        docker logs "${TEST_NAME}-app" || true
+        echo
+        echo "---------------------------------------------------"
+        echo "--------Container Logs: ${TEST_NAME}-tester--------"
+        echo
+        docker logs "${TEST_NAME}-tester" || true
+        echo
+        echo "---------------------------------------------------"
       - echo "Cleaning up..."
       - docker stop "${TEST_NAME}-app" || true
       - docker rm --force "${TEST_NAME}-app" || true
       - docker stop "${TEST_NAME}-tester" || true
       - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
+      - docker network rm "${TEST_NAME}-network" || true
diff --git a/tests/integration/codebuild/buildspec.os.ubuntu.1.yml b/tests/integration/codebuild/buildspec.os.ubuntu.1.yml
deleted file mode 100644
index 655e443..0000000
--- a/tests/integration/codebuild/buildspec.os.ubuntu.1.yml
+++ /dev/null
@@ -1,100 +0,0 @@
-version: 0.2
-
-env:
-  variables:
-    OS_DISTRIBUTION: ubuntu
-    PYTHON_LOCATION: "/usr/bin/python"
-    TEST_NAME: "aws-lambda-python-rtc-ubuntu-1-test"
-batch:
-  build-matrix:
-    static:
-      ignore-failure: false
-      env:
-        type: LINUX_CONTAINER
-        privileged-mode: true
-    dynamic:
-      env:
-        variables:
-          DISTRO_VERSION:
-            - "18.04"
-          RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
-phases:
-  pre_build:
-    commands:
-      - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}"
-      - echo "Extracting and including the Runtime Interface Emulator"
-      - SCRATCH_DIR=".scratch"
-      - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
-      - >
-        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
-      - echo "Building image ${IMAGE_TAG}"
-      - >
-        docker build . \
-          -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-          -t "${IMAGE_TAG}" \
-          --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
-  build:
-    commands:
-      - set -x
-      - echo "Running Image ${IMAGE_TAG}"
-      - docker network create "${TEST_NAME}-network"
-      - >
-        docker run \
-          --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}${RUNTIME_VERSION}" \
-          --name "${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
-      - sleep 2
-      - >
-        docker run \
-          --name "${TEST_NAME}-tester" \
-          --env "TARGET=${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10'
-      - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)"
-      - expected='success'
-      - |
-        echo "Response: ${actual}"
-        if [[ "$actual" != "$expected" ]]; then
-          echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
-          exit -1
-        fi
-    finally:
-      - echo "Cleaning up..."
-      - docker stop "${TEST_NAME}-app" || true
-      - docker rm --force "${TEST_NAME}-app" || true
-      - docker stop "${TEST_NAME}-tester" || true
-      - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
diff --git a/tests/integration/codebuild/buildspec.os.ubuntu.2.yml b/tests/integration/codebuild/buildspec.os.ubuntu.2.yml
deleted file mode 100644
index 453c4ab..0000000
--- a/tests/integration/codebuild/buildspec.os.ubuntu.2.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-version: 0.2
-
-env:
-  variables:
-    OS_DISTRIBUTION: ubuntu
-    PYTHON_LOCATION: "/usr/bin/python"
-    TEST_NAME: "aws-lambda-python-rtc-ubuntu-2-test"
-batch:
-  build-matrix:
-    static:
-      ignore-failure: false
-      env:
-        type: LINUX_CONTAINER
-        privileged-mode: true
-    dynamic:
-      env:
-        variables:
-          DISTRO_VERSION:
-            - "20.04"
-          RUNTIME_VERSION:
-            - "3.8"
-            - "3.9"
-phases:
-  pre_build:
-    commands:
-      - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}"
-      - echo "Extracting and including the Runtime Interface Emulator"
-      - SCRATCH_DIR=".scratch"
-      - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
-      - >
-        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
-      - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
-      - echo "Building image ${IMAGE_TAG}"
-      - >
-        docker build . \
-          -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
-          -t "${IMAGE_TAG}" \
-          --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
-  build:
-    commands:
-      - set -x
-      - echo "Running Image ${IMAGE_TAG}"
-      - docker network create "${TEST_NAME}-network"
-      - >
-        docker run \
-          --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}${RUNTIME_VERSION}" \
-          --name "${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
-      - sleep 2
-      - >
-        docker run \
-          --name "${TEST_NAME}-tester" \
-          --env "TARGET=${TEST_NAME}-app" \
-          --network "${TEST_NAME}-network" \
-          --entrypoint="" \
-          "${IMAGE_TAG}" \
-          sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10'
-      - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)"
-      - expected='success'
-      - |
-        echo "Response: ${actual}"
-        if [[ "$actual" != "$expected" ]]; then
-          echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
-          exit -1
-        fi
-    finally:
-      - echo "Cleaning up..."
-      - docker stop "${TEST_NAME}-app" || true
-      - docker rm --force "${TEST_NAME}-app" || true
-      - docker stop "${TEST_NAME}-tester" || true
-      - docker rm --force "${TEST_NAME}-tester" || true
-      - docker network rm "${TEST_NAME}-network" || true
\ No newline at end of file
diff --git a/tests/integration/codebuild/buildspec.os.alpine.3.yml b/tests/integration/codebuild/buildspec.os.ubuntu.yml
similarity index 61%
rename from tests/integration/codebuild/buildspec.os.alpine.3.yml
rename to tests/integration/codebuild/buildspec.os.ubuntu.yml
index bcb64ce..a6e556d 100644
--- a/tests/integration/codebuild/buildspec.os.alpine.3.yml
+++ b/tests/integration/codebuild/buildspec.os.ubuntu.yml
@@ -2,26 +2,27 @@ version: 0.2
 
 env:
   variables:
-    OS_DISTRIBUTION: alpine
-    PYTHON_LOCATION: "/usr/local/bin/python"
-    TEST_NAME: "aws-lambda-python-rtc-alpine-3-test"
+    OS_DISTRIBUTION: ubuntu
+    PYTHON_LOCATION: "/usr/bin/python"
+    TEST_NAME: "aws-lambda-python-rtc-ubuntu-test"
 batch:
   build-matrix:
     static:
       ignore-failure: false
       env:
-        type: LINUX_CONTAINER
         privileged-mode: true
     dynamic:
       env:
         variables:
           DISTRO_VERSION:
-            - "3.12"
+            - "22.04"
+            - "24.04"
           RUNTIME_VERSION:
-            - "3.6"
-            - "3.7"
-            - "3.8"
             - "3.9"
+            - "3.10"
+            - "3.11"
+            - "3.12"
+            - "3.13"
 phases:
   pre_build:
     commands:
@@ -29,45 +30,48 @@ phases:
       - echo "Extracting and including the Runtime Interface Emulator"
       - SCRATCH_DIR=".scratch"
       - mkdir "${SCRATCH_DIR}"
-      - tar -xvf tests/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}"
+      - ARCHITECTURE=$(arch)
       - >
-        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
-          "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
+        if [[ "$ARCHITECTURE" == "x86_64" ]]; then
+            RIE="aws-lambda-rie"
+        elif [[ "$ARCHITECTURE" == "aarch64" ]]; then
+            RIE="aws-lambda-rie-arm64"
+        else
+            echo "Architecture $ARCHITECTURE is not currently supported."
+            exit 1
+        fi
+      - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}"
       - >
-        echo "RUN apk add curl" >> \
+        cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \
+        echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \
           "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp"
       - >
-        if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]];
-        then
-            echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login."
-        else
-            echo "Performing DockerHub login . . ."
-            docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
-        fi
+        echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json
+        service docker restart
       - echo "Building image ${IMAGE_TAG}"
       - >
         docker build . \
           -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \
           -t "${IMAGE_TAG}" \
           --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \
-          --build-arg DISTRO_VERSION="${DISTRO_VERSION}"
+          --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \
+          --load
   build:
     commands:
       - set -x
       - echo "Running Image ${IMAGE_TAG}"
       - docker network create "${TEST_NAME}-network"
+      - PYTHON_LOCATION=${PYTHON_LOCATION}${RUNTIME_VERSION}
       - >
         docker run \
           --detach \
-          -e "PYTHON_LOCATION=${PYTHON_LOCATION}" \
           --name "${TEST_NAME}-app" \
           --network "${TEST_NAME}-network" \
           --entrypoint="" \
           "${IMAGE_TAG}" \
-          sh -c '/usr/bin/aws-lambda-rie ${PYTHON_LOCATION} -m awslambdaric app.handler'
+          sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler"
       - sleep 2
       - >
         docker run \
@@ -83,19 +87,20 @@ phases:
         echo "Response: ${actual}"
         if [[ "$actual" != "$expected" ]]; then
           echo "fail! runtime: $RUNTIME - expected output $expected - got $actual"
-          echo "---------Container Logs: ${TEST_NAME}-app----------"
-          echo
-          docker logs "${TEST_NAME}-app"
-          echo
-          echo "---------------------------------------------------"
-          echo "--------Container Logs: ${TEST_NAME}-tester--------"
-          echo
-          docker logs "${TEST_NAME}-tester"
-          echo
-          echo "---------------------------------------------------"
           exit -1
         fi
     finally:
+      - |
+        echo "---------Container Logs: ${TEST_NAME}-app----------"
+        echo
+        docker logs "${TEST_NAME}-app" || true
+        echo
+        echo "---------------------------------------------------"
+        echo "--------Container Logs: ${TEST_NAME}-tester--------"
+        echo
+        docker logs "${TEST_NAME}-tester" || true
+        echo
+        echo "---------------------------------------------------"
       - echo "Cleaning up..."
       - docker stop "${TEST_NAME}-app" || true
       - docker rm --force "${TEST_NAME}-app" || true
diff --git a/tests/integration/docker/Dockerfile.echo.alpine b/tests/integration/docker/Dockerfile.echo.alpine
index d4f8ae1..f6790fa 100644
--- a/tests/integration/docker/Dockerfile.echo.alpine
+++ b/tests/integration/docker/Dockerfile.echo.alpine
@@ -4,24 +4,25 @@ ARG DISTRO_VERSION
 
 # Stage 1 - bundle base image + runtime interface client
 # Grab a fresh copy of the image and install GCC
-FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine
+FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine
 # Install libstdc++
 RUN apk add --no-cache \
-    libstdc++
+    libstdc++ \
+    binutils
 
 
 # Stage 2 - build function and dependencies
 FROM python-alpine AS build-image
 # Install aws-lambda-cpp build dependencies
 RUN apk add --no-cache \
-        build-base \
-        libtool \
-        autoconf \
-        automake \
-        libexecinfo-dev \
-        make \
-        cmake \
-        libcurl
+    build-base \
+    libtool \
+    autoconf \
+    automake \
+    elfutils-dev \
+    make \
+    cmake \
+    libcurl
 
 # Include global args in this stage of the build
 ARG RIC_BUILD_DIR="/home/build/"
@@ -30,6 +31,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
 # Copy function code and Runtime Interface Client .tgz
 WORKDIR ${RIC_BUILD_DIR}
 COPY . .
+RUN pip3 install setuptools
 RUN make init build test && \
     mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
 
@@ -39,15 +41,12 @@ ARG FUNCTION_DIR="/home/app/"
 RUN mkdir -p ${FUNCTION_DIR}
 # Copy function code
 COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR}
-# Copy Runtime Interface Client .tgz
-RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
 
 # Install the function's dependencies
 WORKDIR ${FUNCTION_DIR}
 RUN python${RUNTIME_VERSION} -m pip install \
-        awslambdaric-test.tar.gz \
-        --target ${FUNCTION_DIR} && \
-    rm awslambdaric-test.tar.gz
+        ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz \
+        --target ${FUNCTION_DIR}
 
 
 # Stage 3 - final runtime interface client image
diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux b/tests/integration/docker/Dockerfile.echo.amazonlinux2
similarity index 91%
rename from tests/integration/docker/Dockerfile.echo.amazonlinux
rename to tests/integration/docker/Dockerfile.echo.amazonlinux2
index 701420a..be05aa1 100644
--- a/tests/integration/docker/Dockerfile.echo.amazonlinux
+++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2
@@ -1,8 +1,7 @@
 ARG DISTRO_VERSION
-
 # Stage 1 - bundle base image + runtime interface client
 # Grab a fresh copy of the image and install Python
-FROM amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder
 
 ARG RUNTIME_VERSION
 
@@ -18,8 +17,10 @@ RUN yum install -y \
   freetype-devel \
   yum-utils \
   findutils \
-  openssl-devel \
   wget \
+  openssl11 \
+  openssl11-devel \
+  bzip2-devel \
   libffi-devel \
   sqlite-devel
 
@@ -39,7 +40,7 @@ RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/f
   && ln -s /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python${RUNTIME_LATEST_VERSION}
 
 # Stage 2 - clean python build dependencies
-FROM amazonlinux:${DISTRO_VERSION} AS python-amazonlinux
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux
 RUN yum install -y \
   libffi-devel
 
@@ -50,6 +51,7 @@ ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
 # Stage 3 - build function and dependencies
 FROM python-amazonlinux-builder AS build-image
 ARG RUNTIME_VERSION
+ARG ARCHITECTURE
 
 # Install aws-lambda-cpp build dependencies
 RUN yum install -y \
@@ -64,7 +66,7 @@ RUN yum install -y \
   wget
 
 # Install a modern CMake
-RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.18.4/cmake-3.18.4-Linux-x86_64.sh && \
+RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \
     sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory;
 
 ENV PATH=/usr/local/bin:$PATH
@@ -78,6 +80,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
 # Copy function code and Runtime Interface Client .tgz
 WORKDIR ${RIC_BUILD_DIR}
 COPY . .
+
 RUN make init build test && \
     mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
 
diff --git a/tests/integration/docker/Dockerfile.echo.centos b/tests/integration/docker/Dockerfile.echo.amazonlinux2023
similarity index 75%
rename from tests/integration/docker/Dockerfile.echo.centos
rename to tests/integration/docker/Dockerfile.echo.amazonlinux2023
index 62bcc61..16bbc79 100644
--- a/tests/integration/docker/Dockerfile.echo.centos
+++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2023
@@ -1,13 +1,12 @@
 ARG DISTRO_VERSION
-
 # Stage 1 - bundle base image + runtime interface client
 # Grab a fresh copy of the image and install Python
-FROM centos:${DISTRO_VERSION} AS python-centos-builder
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder
 
 ARG RUNTIME_VERSION
 
 # Install apt dependencies
-RUN yum install -y \
+RUN dnf install -y \
   gcc \
   gcc-c++ \
   tar \
@@ -18,8 +17,10 @@ RUN yum install -y \
   freetype-devel \
   yum-utils \
   findutils \
-  openssl-devel \
   wget \
+  openssl \
+  openssl-devel \
+  bzip2-devel \
   libffi-devel \
   sqlite-devel
 
@@ -39,20 +40,21 @@ RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/f
   && ln -s /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python${RUNTIME_LATEST_VERSION}
 
 # Stage 2 - clean python build dependencies
-FROM centos:${DISTRO_VERSION} AS python-centos
-RUN yum install -y \
+FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux
+RUN dnf install -y \
   libffi-devel
 
 # Copy the compiled python to /usr/local
-COPY --from=python-centos-builder /usr/local /usr/local
+COPY --from=python-amazonlinux-builder /usr/local /usr/local
 ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
 
 # Stage 3 - build function and dependencies
-FROM python-centos-builder AS build-image
+FROM python-amazonlinux-builder AS build-image
 ARG RUNTIME_VERSION
+ARG ARCHITECTURE
 
 # Install aws-lambda-cpp build dependencies
-RUN yum install -y \
+RUN dnf install -y \
   tar \
   gzip \
   make \
@@ -61,10 +63,11 @@ RUN yum install -y \
   libtool \
   libcurl-devel \
   gcc-c++ \
-  wget
+  wget \
+  sqlite-devel
 
 # Install a modern CMake
-RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.18.4/cmake-3.18.4-Linux-x86_64.sh && \
+RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \
     sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory;
 
 ENV PATH=/usr/local/bin:$PATH
@@ -78,8 +81,19 @@ RUN mkdir -p ${RIC_BUILD_DIR}
 # Copy function code and Runtime Interface Client .tgz
 WORKDIR ${RIC_BUILD_DIR}
 COPY . .
-RUN make init build && \
-    mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
+
+# distutils no longer available in python3.12 and later
+#   https://docs.python.org/3/whatsnew/3.12.html
+#   https://peps.python.org/pep-0632/
+RUN pip3 install setuptools
+RUN make init build 
+
+RUN mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
+RUN python${RUNTIME_VERSION} -m pip install \
+      ./dist/awslambdaric-test.tar.gz \
+      --target ${RIC_BUILD_DIR}
+
+RUN make test
 
 # Include global args in this stage of the build
 ARG FUNCTION_DIR="/home/app/"
@@ -92,19 +106,16 @@ RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
 
 # Install the function's dependencies
 WORKDIR ${FUNCTION_DIR}
-ARG ENABLE_LTO=OFF
-ENV ENABLE_LTO ${ENABLE_LTO}
 RUN python${RUNTIME_VERSION} -m pip install \
-          awslambdaric-test.tar.gz \
-          --verbose \
-          --target ${FUNCTION_DIR} && \
+        awslambdaric-test.tar.gz \
+        --target ${FUNCTION_DIR} && \
     rm awslambdaric-test.tar.gz
 
 
 # Stage 4 - final runtime interface client image
 # Grab a fresh copy of the Python image
-FROM python-centos
-
+FROM python-amazonlinux
+RUN dnf install -y brotli
 # Include global arg in this stage of the build
 ARG FUNCTION_DIR="/home/app/"
 # Set working directory to function root directory
diff --git a/tests/integration/docker/Dockerfile.echo.debian b/tests/integration/docker/Dockerfile.echo.debian
index 8842792..bf0f4fa 100644
--- a/tests/integration/docker/Dockerfile.echo.debian
+++ b/tests/integration/docker/Dockerfile.echo.debian
@@ -2,7 +2,7 @@ ARG RUNTIME_VERSION
 ARG DISTRO_VERSION
 
 # Stage 1 - build function and dependencies
-FROM python:${RUNTIME_VERSION}-${DISTRO_VERSION} AS python-debian-builder
+FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-${DISTRO_VERSION} AS python-debian-builder
 
 # Install aws-lambda-cpp build dependencies
 RUN apt-get update && \
@@ -19,6 +19,7 @@ RUN mkdir -p ${RIC_BUILD_DIR}
 # Copy function code and Runtime Interface Client .tgz
 WORKDIR ${RIC_BUILD_DIR}
 COPY . .
+RUN pip3 install setuptools
 RUN make init build test && \
     mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
 
@@ -40,8 +41,8 @@ RUN pip install \
 
 
 # Stage 2 - final runtime interface client image
-# Grab a fresh slim copy of the Node image
-FROM python:${RUNTIME_VERSION}-slim-${DISTRO_VERSION}
+# Grab a fresh slim copy of the Python image
+FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-slim-${DISTRO_VERSION}
 
 # Include global arg in this stage of the build
 ARG FUNCTION_DIR="/home/app/"
diff --git a/tests/integration/docker/Dockerfile.echo.ubuntu b/tests/integration/docker/Dockerfile.echo.ubuntu
index 83e4a6e..0ce3000 100644
--- a/tests/integration/docker/Dockerfile.echo.ubuntu
+++ b/tests/integration/docker/Dockerfile.echo.ubuntu
@@ -2,43 +2,48 @@ ARG DISTRO_VERSION
 
 # Stage 1 - bundle base image + runtime interface client
 # Grab a fresh copy of the image and install Python
-FROM ubuntu:${DISTRO_VERSION} AS python-image
+FROM public.ecr.aws/ubuntu/ubuntu:${DISTRO_VERSION} AS python-image
 
 ENV DEBIAN_FRONTEND=noninteractive
 
 ARG RUNTIME_VERSION
 
 # Install python and pip
+RUN apt-get update && apt-get install -y software-properties-common
+RUN add-apt-repository ppa:deadsnakes/ppa
 RUN apt-get update && \
-  apt-get install -y \
-    curl \
-    python${RUNTIME_VERSION} \
-    python${RUNTIME_VERSION}-distutils
+    apt-get install -y \
+        curl \
+        python${RUNTIME_VERSION} \
+        python3-pip \
+        python3-virtualenv
+
+# python3xx-distutils is needed for python < 3.12
+RUN if [ $(echo ${RUNTIME_VERSION} | cut -d '.' -f 2) -lt 12 ]; then \
+            apt-get install -y python${RUNTIME_VERSION}-distutils; \
+    fi
+RUN virtualenv --python /usr/bin/python${RUNTIME_VERSION} --no-setuptools /home/venv
+
 
-RUN ln -s /usr/bin/python${RUNTIME_VERSION} /usr/local/bin/python3
 
 # Stage 2 - build function and dependencies
 FROM python-image AS python-ubuntu-builder
 
 ARG RUNTIME_VERSION
 
-RUN curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
-RUN python${RUNTIME_VERSION} get-pip.py
-
 # Install aws-lambda-cpp build dependencies
-RUN apt-get update && \
-  apt-get install -y \
-    g++ \
-    gcc \
-    tar \
-    gzip \
-    make \
-    cmake \
-    autoconf \
-    automake \
-    libtool \
-    libcurl4-openssl-dev \
-    python${RUNTIME_VERSION}-dev
+RUN apt-get install -y \
+        g++ \
+        gcc \
+        tar \
+        gzip \
+        make \
+        cmake \
+        autoconf \
+        automake \
+        libtool \
+        libcurl4-openssl-dev \
+        python${RUNTIME_VERSION}-dev
 
 # Include global args in this stage of the build
 ARG RIC_BUILD_DIR="/home/build/"
@@ -47,27 +52,28 @@ RUN mkdir -p ${RIC_BUILD_DIR}
 # Copy function code and Runtime Interface Client .tgz
 WORKDIR ${RIC_BUILD_DIR}
 COPY . .
-RUN make init build test && \
+RUN . /home/venv/bin/activate && \
+    pip install setuptools && \
+    make init build test && \
     mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz
 
+
+
 # Include global args in this stage of the build
 ARG FUNCTION_DIR="/home/app/"
 # Create function directory
 RUN mkdir -p ${FUNCTION_DIR}
 # Copy function code
 COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR}
-# Copy Runtime Interface Client .tgz
-RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz
-
 # Install the function's dependencies
 WORKDIR ${FUNCTION_DIR}
-RUN python${RUNTIME_VERSION} -m pip install \
-        awslambdaric-test.tar.gz \
-        --target ${FUNCTION_DIR} && \
-    rm awslambdaric-test.tar.gz
+RUN . /home/venv/bin/activate && \
+    pip install ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz --target ${FUNCTION_DIR}
+
+
 
 
-# Stage 4 - final runtime interface client image
+# Stage 3 - final runtime interface client image
 # Grab a fresh copy of the Python image
 FROM python-image
 
diff --git a/tests/integration/resources/aws-lambda-rie-arm64.tar.gz b/tests/integration/resources/aws-lambda-rie-arm64.tar.gz
new file mode 100644
index 0000000..f62577f
Binary files /dev/null and b/tests/integration/resources/aws-lambda-rie-arm64.tar.gz differ
diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py
index b6f4b23..33afb1c 100644
--- a/tests/test_bootstrap.py
+++ b/tests/test_bootstrap.py
@@ -4,18 +4,30 @@
 
 import importlib
 import json
+import logging
+import logging.config
 import os
 import re
 import tempfile
+import time
 import traceback
 import unittest
 from io import StringIO
 from tempfile import NamedTemporaryFile
-from unittest.mock import MagicMock, Mock, patch
+from unittest.mock import MagicMock, Mock, patch, ANY
 
 import awslambdaric.bootstrap as bootstrap
 from awslambdaric.lambda_runtime_exception import FaultException
+from awslambdaric.lambda_runtime_log_utils import (
+    LogFormat,
+    _get_log_level_from_env_var,
+    JsonFormatter,
+)
 from awslambdaric.lambda_runtime_marshaller import LambdaMarshaller
+from awslambdaric.lambda_literals import (
+    lambda_unhandled_exception_warning_message,
+)
+import snapshot_restore_py
 
 
 class TestUpdateXrayEnv(unittest.TestCase):
@@ -53,6 +65,14 @@ def setUp(self):
         self.event_body = '"event_body"'
         self.working_directory = os.getcwd()
 
+        logging.getLogger().handlers.clear()
+
+    def tearDown(self) -> None:
+        logging.getLogger().handlers.clear()
+        logging.getLogger().level = logging.NOTSET
+
+        return super().tearDown()
+
     @staticmethod
     def dummy_handler(json_input, lambda_context):
         return {"input": json_input, "aws_request_id": lambda_context.aws_request_id}
@@ -68,6 +88,7 @@ def test_handle_event_request_happy_case(self):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         self.lambda_runtime.post_invocation_result.assert_called_once_with(
@@ -91,6 +112,7 @@ def test_handle_event_request_invalid_client_context(self):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -132,6 +154,7 @@ def test_handle_event_request_invalid_cognito_idenity(self):
             "invalid_cognito_identity",
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -174,6 +197,7 @@ def test_handle_event_request_invalid_event_body(self):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -218,6 +242,7 @@ def invalid_json_response(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -263,6 +288,7 @@ def __init__(self, message):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -315,6 +341,7 @@ def __init__(self, message):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -366,6 +393,7 @@ def unable_to_import_module(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -405,6 +433,7 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         args, _ = self.lambda_runtime.post_invocation_error.call_args
@@ -443,6 +472,8 @@ def raise_exception_handler(json_input, lambda_context):
                     ),
                 )
 
+        logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
         bootstrap.handle_event_request(
             self.lambda_runtime,
             raise_exception_handler,
@@ -453,6 +484,7 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
 
@@ -476,6 +508,8 @@ def raise_exception_handler(json_input, lambda_context):
                     "FaultExceptionType", "Fault exception msg", None
                 )
 
+        logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
         bootstrap.handle_event_request(
             self.lambda_runtime,
             raise_exception_handler,
@@ -486,6 +520,7 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         error_logs = "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n"
@@ -502,6 +537,8 @@ def raise_exception_handler(json_input, lambda_context):
             except ImportError:
                 raise bootstrap.FaultException("FaultExceptionType", None, None)
 
+        logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
         bootstrap.handle_event_request(
             self.lambda_runtime,
             raise_exception_handler,
@@ -512,6 +549,7 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n"
@@ -528,6 +566,8 @@ def raise_exception_handler(json_input, lambda_context):
             except ImportError:
                 raise bootstrap.FaultException(None, "Fault exception msg", None)
 
+        logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
         bootstrap.handle_event_request(
             self.lambda_runtime,
             raise_exception_handler,
@@ -538,6 +578,7 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
         error_logs = "[ERROR] Fault exception msg\rTraceback (most recent call last):\n"
@@ -563,6 +604,8 @@ def raise_exception_handler(json_input, lambda_context):
                     ),
                 )
 
+        logging.getLogger().addHandler(logging.StreamHandler(mock_stdout))
+
         bootstrap.handle_event_request(
             self.lambda_runtime,
             raise_exception_handler,
@@ -573,9 +616,9 @@ def raise_exception_handler(json_input, lambda_context):
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
-
         error_logs = "[ERROR]\r"
         error_logs += "Traceback (most recent call last):\r"
         error_logs += '  File "spam.py", line 3, in <module>\r'
@@ -586,22 +629,20 @@ def raise_exception_handler(json_input, lambda_context):
         self.assertEqual(mock_stdout.getvalue(), error_logs)
 
     @patch("sys.stdout", new_callable=StringIO)
-    @patch("importlib.import_module")
-    def test_handle_event_request_fault_exception_logging_syntax_error(
-        self, mock_import_module, mock_stdout
-    ):
-        try:
-            eval("-")
-        except SyntaxError as e:
-            syntax_error = e
-
-        mock_import_module.side_effect = syntax_error
+    def test_handle_event_request_fault_exception_logging_in_json(self, mock_stdout):
+        def raise_exception_handler(json_input, lambda_context):
+            try:
+                import invalid_module  # noqa: F401
+            except ImportError:
+                raise bootstrap.FaultException("FaultExceptionType", None, None)
 
-        response_handler = bootstrap._get_handler("a.b")
+        logging_handler = logging.StreamHandler(mock_stdout)
+        logging_handler.setFormatter(JsonFormatter())
+        logging.getLogger().addHandler(logging_handler)
 
         bootstrap.handle_event_request(
             self.lambda_runtime,
-            response_handler,
+            raise_exception_handler,
             "invoke_id",
             self.event_body,
             "application/json",
@@ -609,22 +650,16 @@ def test_handle_event_request_fault_exception_logging_syntax_error(
             {},
             "invoked_function_arn",
             0,
+            "tenant_id",
             bootstrap.StandardLogSink(),
         )
 
-        import sys
-
-        sys.stderr.write(mock_stdout.getvalue())
+        stdout_value = mock_stdout.getvalue()
 
-        error_logs = (
-            "[ERROR] Runtime.UserCodeSyntaxError: Syntax error in module 'a': "
-            "unexpected EOF while parsing (<string>, line 1)\r"
-        )
-        error_logs += "Traceback (most recent call last):\r"
-        error_logs += '  File "<string>" Line 1\r'
-        error_logs += "    -\n"
+        # this line is not in json because of the way the test runtime is bootstrapped
+        error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n"
 
-        self.assertEqual(mock_stdout.getvalue(), error_logs)
+        self.assertEqual(stdout_value, error_logs)
 
 
 class TestXrayFault(unittest.TestCase):
@@ -703,10 +738,8 @@ def __eq__(self, other):
 
     def test_get_event_handler_bad_handler(self):
         handler_name = "bad_handler"
-        response_handler = bootstrap._get_handler(handler_name)
         with self.assertRaises(FaultException) as cm:
-            response_handler()
-
+            response_handler = bootstrap._get_handler(handler_name)
         returned_exception = cm.exception
         self.assertEqual(
             self.FaultExceptionMatcher(
@@ -718,9 +751,8 @@ def test_get_event_handler_bad_handler(self):
 
     def test_get_event_handler_import_error(self):
         handler_name = "no_module.handler"
-        response_handler = bootstrap._get_handler(handler_name)
         with self.assertRaises(FaultException) as cm:
-            response_handler()
+            response_handler = bootstrap._get_handler(handler_name)
         returned_exception = cm.exception
         self.assertEqual(
             self.FaultExceptionMatcher(
@@ -743,10 +775,9 @@ def test_get_event_handler_syntax_error(self):
             filename_w_ext = os.path.basename(tmp_file.name)
             filename, _ = os.path.splitext(filename_w_ext)
             handler_name = "{}.syntax_error".format(filename)
-            response_handler = bootstrap._get_handler(handler_name)
 
             with self.assertRaises(FaultException) as cm:
-                response_handler()
+                response_handler = bootstrap._get_handler(handler_name)
             returned_exception = cm.exception
             self.assertEqual(
                 self.FaultExceptionMatcher(
@@ -768,9 +799,8 @@ def test_get_event_handler_missing_error(self):
             filename_w_ext = os.path.basename(tmp_file.name)
             filename, _ = os.path.splitext(filename_w_ext)
             handler_name = "{}.my_handler".format(filename)
-            response_handler = bootstrap._get_handler(handler_name)
             with self.assertRaises(FaultException) as cm:
-                response_handler()
+                response_handler = bootstrap._get_handler(handler_name)
             returned_exception = cm.exception
             self.assertEqual(
                 self.FaultExceptionMatcher(
@@ -787,9 +817,8 @@ def test_get_event_handler_slash(self):
         response_handler()
 
     def test_get_event_handler_build_in_conflict(self):
-        response_handler = bootstrap._get_handler("sys.hello")
         with self.assertRaises(FaultException) as cm:
-            response_handler()
+            response_handler = bootstrap._get_handler("sys.hello")
         returned_exception = cm.exception
         self.assertEqual(
             self.FaultExceptionMatcher(
@@ -828,6 +857,7 @@ def test_application_json(self):
             cognito_identity_json=None,
             invoked_function_arn="invocation-arn",
             epoch_deadline_time_in_ms=1415836801003,
+            tenant_id=None,
             log_sink=bootstrap.StandardLogSink(),
         )
 
@@ -847,6 +877,7 @@ def test_binary_request_binary_response(self):
             cognito_identity_json=None,
             invoked_function_arn="invocation-arn",
             epoch_deadline_time_in_ms=1415836801003,
+            tenant_id=None,
             log_sink=bootstrap.StandardLogSink(),
         )
 
@@ -866,6 +897,7 @@ def test_json_request_binary_response(self):
             cognito_identity_json=None,
             invoked_function_arn="invocation-arn",
             epoch_deadline_time_in_ms=1415836801003,
+            tenant_id=None,
             log_sink=bootstrap.StandardLogSink(),
         )
 
@@ -884,6 +916,7 @@ def test_binary_with_application_json(self):
             cognito_identity_json=None,
             invoked_function_arn="invocation-arn",
             epoch_deadline_time_in_ms=1415836801003,
+            tenant_id=None,
             log_sink=bootstrap.StandardLogSink(),
         )
 
@@ -914,9 +947,13 @@ def test_log_error_standard_log_sink(self, mock_stdout):
 
     def test_log_error_framed_log_sink(self):
         with NamedTemporaryFile() as temp_file:
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as log_sink:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as log_sink:
                 err_to_log = bootstrap.make_error("Error message", "ErrorType", None)
                 bootstrap.log_error(err_to_log, log_sink)
+            after = int(time.time_ns() / 1000)
 
             expected_logged_error = (
                 "[ERROR] ErrorType: Error message\nTraceback (most recent call last):"
@@ -926,12 +963,16 @@ def test_log_error_framed_log_sink(self):
                 content = f.read()
 
                 frame_type = int.from_bytes(content[:4], "big")
-                self.assertEqual(frame_type, 0xA55A0001)
+                self.assertEqual(frame_type, 0xA55A0017)
 
                 length = int.from_bytes(content[4:8], "big")
                 self.assertEqual(length, len(expected_logged_error.encode("utf8")))
 
-                actual_message = content[8:].decode()
+                timestamp = int.from_bytes(content[8:16], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+                actual_message = content[16:].decode()
                 self.assertEqual(actual_message, expected_logged_error)
 
     @patch("sys.stdout", new_callable=StringIO)
@@ -949,11 +990,15 @@ def test_log_error_indentation_standard_log_sink(self, mock_stdout):
 
     def test_log_error_indentation_framed_log_sink(self):
         with NamedTemporaryFile() as temp_file:
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as log_sink:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as log_sink:
                 err_to_log = bootstrap.make_error(
                     "Error message", "ErrorType", ["  line1  ", "  line2  ", "  "]
                 )
                 bootstrap.log_error(err_to_log, log_sink)
+            after = int(time.time_ns() / 1000)
 
             expected_logged_error = (
                 "[ERROR] ErrorType: Error message\nTraceback (most recent call last):"
@@ -964,12 +1009,16 @@ def test_log_error_indentation_framed_log_sink(self):
                 content = f.read()
 
                 frame_type = int.from_bytes(content[:4], "big")
-                self.assertEqual(frame_type, 0xA55A0001)
+                self.assertEqual(frame_type, 0xA55A0017)
 
                 length = int.from_bytes(content[4:8], "big")
                 self.assertEqual(length, len(expected_logged_error.encode("utf8")))
 
-                actual_message = content[8:].decode()
+                timestamp = int.from_bytes(content[8:16], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+                actual_message = content[16:].decode()
                 self.assertEqual(actual_message, expected_logged_error)
 
     @patch("sys.stdout", new_callable=StringIO)
@@ -984,11 +1033,15 @@ def test_log_error_empty_stacktrace_line_standard_log_sink(self, mock_stdout):
 
     def test_log_error_empty_stacktrace_line_framed_log_sink(self):
         with NamedTemporaryFile() as temp_file:
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as log_sink:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as log_sink:
                 err_to_log = bootstrap.make_error(
                     "Error message", "ErrorType", ["line1", "", "line2"]
                 )
                 bootstrap.log_error(err_to_log, log_sink)
+            after = int(time.time_ns() / 1000)
 
             expected_logged_error = (
                 "[ERROR] ErrorType: Error message\nTraceback "
@@ -999,18 +1052,25 @@ def test_log_error_empty_stacktrace_line_framed_log_sink(self):
                 content = f.read()
 
                 frame_type = int.from_bytes(content[:4], "big")
-                self.assertEqual(frame_type, 0xA55A0001)
+                self.assertEqual(frame_type, 0xA55A0017)
 
                 length = int.from_bytes(content[4:8], "big")
                 self.assertEqual(length, len(expected_logged_error))
 
-                actual_message = content[8:].decode()
+                timestamp = int.from_bytes(content[8:16], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+                actual_message = content[16:].decode()
                 self.assertEqual(actual_message, expected_logged_error)
 
     # Just to ensure we are not logging the requestId from error response, just sending in the response
     def test_log_error_invokeId_line_framed_log_sink(self):
         with NamedTemporaryFile() as temp_file:
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as log_sink:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as log_sink:
                 err_to_log = bootstrap.make_error(
                     "Error message",
                     "ErrorType",
@@ -1018,6 +1078,7 @@ def test_log_error_invokeId_line_framed_log_sink(self):
                     "testrequestId",
                 )
                 bootstrap.log_error(err_to_log, log_sink)
+            after = int(time.time_ns() / 1000)
 
             expected_logged_error = (
                 "[ERROR] ErrorType: Error message\nTraceback "
@@ -1028,12 +1089,16 @@ def test_log_error_invokeId_line_framed_log_sink(self):
                 content = f.read()
 
                 frame_type = int.from_bytes(content[:4], "big")
-                self.assertEqual(frame_type, 0xA55A0001)
+                self.assertEqual(frame_type, 0xA55A0017)
 
                 length = int.from_bytes(content[4:8], "big")
                 self.assertEqual(length, len(expected_logged_error))
 
-                actual_message = content[8:].decode()
+                timestamp = int.from_bytes(content[8:16], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+                actual_message = content[16:].decode()
                 self.assertEqual(actual_message, expected_logged_error)
 
 
@@ -1070,30 +1135,38 @@ def test_create_unbuffered_log_sinks(self, mock_stdout):
         self.assertEqual(mock_stdout.getvalue(), "log")
 
     def test_create_framed_telemetry_log_sinks(self):
-        fd = "test_fd"
-        os.environ["_LAMBDA_TELEMETRY_LOG_FD"] = fd
+        fd = 3
+        os.environ["_LAMBDA_TELEMETRY_LOG_FD"] = "3"
 
         actual = bootstrap.create_log_sink()
 
         self.assertIsInstance(actual, bootstrap.FramedTelemetryLogSink)
-        self.assertEqual(actual.filename, "/proc/self/fd/" + fd)
+        self.assertEqual(actual.fd, fd)
         self.assertFalse("_LAMBDA_TELEMETRY_LOG_FD" in os.environ)
 
     def test_single_frame(self):
         with NamedTemporaryFile() as temp_file:
             message = "hello world\nsomething on a new line!\n"
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as ls:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as ls:
                 ls.log(message)
+            after = int(time.time_ns() / 1000)
             with open(temp_file.name, "rb") as f:
                 content = f.read()
 
                 frame_type = int.from_bytes(content[:4], "big")
-                self.assertEqual(frame_type, 0xA55A0001)
+                self.assertEqual(frame_type, 0xA55A0003)
 
                 length = int.from_bytes(content[4:8], "big")
                 self.assertEqual(length, len(message))
 
-                actual_message = content[8:].decode()
+                timestamp = int.from_bytes(content[8:16], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+                actual_message = content[16:].decode()
                 self.assertEqual(actual_message, message)
 
     def test_multiple_frame(self):
@@ -1101,22 +1174,31 @@ def test_multiple_frame(self):
             first_message = "hello world\nsomething on a new line!"
             second_message = "hello again\nhere's another message\n"
 
-            with bootstrap.FramedTelemetryLogSink(temp_file.name) as ls:
+            before = int(time.time_ns() / 1000)
+            with bootstrap.FramedTelemetryLogSink(
+                os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+            ) as ls:
                 ls.log(first_message)
                 ls.log(second_message)
+            after = int(time.time_ns() / 1000)
 
             with open(temp_file.name, "rb") as f:
                 content = f.read()
                 pos = 0
                 for message in [first_message, second_message]:
                     frame_type = int.from_bytes(content[pos : pos + 4], "big")
-                    self.assertEqual(frame_type, 0xA55A0001)
+                    self.assertEqual(frame_type, 0xA55A0003)
                     pos += 4
 
                     length = int.from_bytes(content[pos : pos + 4], "big")
                     self.assertEqual(length, len(message))
                     pos += 4
 
+                    timestamp = int.from_bytes(content[pos : pos + 8], "big")
+                    self.assertTrue(before <= timestamp)
+                    self.assertTrue(timestamp <= after)
+                    pos += 8
+
                     actual_message = content[pos : pos + len(message)].decode()
                     self.assertEqual(actual_message, message)
                     pos += len(message)
@@ -1124,10 +1206,298 @@ def test_multiple_frame(self):
                 self.assertEqual(content[pos:], b"")
 
 
+class TestLoggingSetup(unittest.TestCase):
+    def test_log_level(self) -> None:
+        test_cases = [
+            (LogFormat.JSON, "TRACE", logging.DEBUG),
+            (LogFormat.JSON, "DEBUG", logging.DEBUG),
+            (LogFormat.JSON, "INFO", logging.INFO),
+            (LogFormat.JSON, "WARN", logging.WARNING),
+            (LogFormat.JSON, "ERROR", logging.ERROR),
+            (LogFormat.JSON, "FATAL", logging.CRITICAL),
+            (LogFormat.TEXT, "TRACE", logging.DEBUG),
+            (LogFormat.TEXT, "DEBUG", logging.DEBUG),
+            (LogFormat.TEXT, "INFO", logging.INFO),
+            (LogFormat.TEXT, "WARN", logging.WARN),
+            (LogFormat.TEXT, "ERROR", logging.ERROR),
+            (LogFormat.TEXT, "FATAL", logging.CRITICAL),
+            ("Unknown format", "INFO", logging.INFO),
+            # if level is unknown fall back to default
+            (LogFormat.JSON, "Unknown level", logging.NOTSET),
+        ]
+        for fmt, log_level, expected_level in test_cases:
+            with self.subTest():
+                # Drop previous setup
+                logging.getLogger().handlers.clear()
+                logging.getLogger().level = logging.NOTSET
+
+                bootstrap._setup_logging(
+                    fmt,
+                    _get_log_level_from_env_var(log_level),
+                    bootstrap.StandardLogSink(),
+                )
+
+                self.assertEqual(expected_level, logging.getLogger().level)
+
+
+class TestLambdaLoggerHandlerSetup(unittest.TestCase):
+    @classmethod
+    def tearDownClass(cls):
+        importlib.reload(bootstrap)
+        logging.getLogger().handlers.clear()
+        logging.getLogger().level = logging.NOTSET
+
+    def test_handler_setup(self, *_):
+        test_cases = [
+            (62, 0xA55A0003, 46, {}),
+            (133, 0xA55A001A, 117, {"AWS_LAMBDA_LOG_FORMAT": "JSON"}),
+            (62, 0xA55A001B, 46, {"AWS_LAMBDA_LOG_LEVEL": "INFO"}),
+        ]
+
+        for total_length, header, message_length, env_vars in test_cases:
+            with patch.dict(
+                os.environ, env_vars, clear=True
+            ), NamedTemporaryFile() as temp_file:
+                importlib.reload(bootstrap)
+                logging.getLogger().handlers.clear()
+                logging.getLogger().level = logging.NOTSET
+
+                before = int(time.time_ns() / 1000)
+                with bootstrap.FramedTelemetryLogSink(
+                    os.open(temp_file.name, os.O_CREAT | os.O_RDWR)
+                ) as ls:
+                    bootstrap._setup_logging(
+                        bootstrap._AWS_LAMBDA_LOG_FORMAT,
+                        bootstrap._AWS_LAMBDA_LOG_LEVEL,
+                        ls,
+                    )
+                    logger = logging.getLogger()
+                    logger.critical("critical")
+                after = int(time.time_ns() / 1000)
+
+                content = open(temp_file.name, "rb").read()
+                self.assertEqual(len(content), total_length)
+
+                pos = 0
+                frame_type = int.from_bytes(content[pos : pos + 4], "big")
+                self.assertEqual(frame_type, header)
+                pos += 4
+
+                length = int.from_bytes(content[pos : pos + 4], "big")
+                self.assertEqual(length, message_length)
+                pos += 4
+
+                timestamp = int.from_bytes(content[pos : pos + 8], "big")
+                self.assertTrue(before <= timestamp)
+                self.assertTrue(timestamp <= after)
+
+
+class TestLogging(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls) -> None:
+        logging.getLogger().handlers.clear()
+        logging.getLogger().level = logging.NOTSET
+        bootstrap._setup_logging(
+            LogFormat.from_str("JSON"), "INFO", bootstrap.StandardLogSink()
+        )
+
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_json_formatter(self, mock_stderr):
+        logger = logging.getLogger("a.b")
+
+        test_cases = [
+            (
+                logging.ERROR,
+                "TEST 1",
+                {
+                    "level": "ERROR",
+                    "logger": "a.b",
+                    "message": "TEST 1",
+                    "requestId": "",
+                },
+            ),
+            (
+                logging.ERROR,
+                "test \nwith \nnew \nlines",
+                {
+                    "level": "ERROR",
+                    "logger": "a.b",
+                    "message": "test \nwith \nnew \nlines",
+                    "requestId": "",
+                },
+            ),
+            (
+                logging.CRITICAL,
+                "TEST CRITICAL",
+                {
+                    "level": "CRITICAL",
+                    "logger": "a.b",
+                    "message": "TEST CRITICAL",
+                    "requestId": "",
+                },
+            ),
+        ]
+        for level, msg, expected in test_cases:
+            with self.subTest(msg):
+                with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
+                    logger.log(level, msg)
+
+                    data = json.loads(mock_stdout.getvalue())
+                    data.pop("timestamp")
+                    self.assertEqual(
+                        data,
+                        expected,
+                    )
+        self.assertEqual(mock_stderr.getvalue(), "")
+
+    @patch("awslambdaric.bootstrap._GLOBAL_TENANT_ID", "test-tenant-id")
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_json_formatter_with_tenant_id(self, mock_stderr):
+        logger = logging.getLogger("a.b")
+        level = logging.INFO
+        message = "Test json formatting with tenant id"
+        expected = {
+            "level": "INFO",
+            "logger": "a.b",
+            "message": message,
+            "requestId": "",
+            "tenantId": "test-tenant-id",
+        }
+
+        with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
+            logger.log(level, message)
+
+            data = json.loads(mock_stdout.getvalue())
+            data.pop("timestamp")
+            self.assertEqual(
+                data,
+                expected,
+            )
+        self.assertEqual(mock_stderr.getvalue(), "")
+
+    @patch("sys.stdout", new_callable=StringIO)
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_exception(self, mock_stderr, mock_stdout):
+        try:
+            raise ValueError("error message")
+        except ValueError:
+            logging.getLogger("test.logger").exception("test exception")
+
+        exception_log = json.loads(mock_stdout.getvalue())
+        self.assertIn("location", exception_log)
+        self.assertIn("stackTrace", exception_log)
+        exception_log.pop("timestamp")
+        exception_log.pop("location")
+        stack_trace = exception_log.pop("stackTrace")
+
+        self.assertEqual(len(stack_trace), 1)
+
+        self.assertEqual(
+            exception_log,
+            {
+                "errorMessage": "error message",
+                "errorType": "ValueError",
+                "level": "ERROR",
+                "logger": "test.logger",
+                "message": "test exception",
+                "requestId": "",
+            },
+        )
+
+        self.assertEqual(mock_stderr.getvalue(), "")
+
+    @patch("sys.stdout", new_callable=StringIO)
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_log_level(self, mock_stderr, mock_stdout):
+        logger = logging.getLogger("test.logger")
+
+        logger.debug("debug message")
+        logger.info("info message")
+
+        data = json.loads(mock_stdout.getvalue())
+        data.pop("timestamp")
+
+        self.assertEqual(
+            data,
+            {
+                "level": "INFO",
+                "logger": "test.logger",
+                "message": "info message",
+                "requestId": "",
+            },
+        )
+        self.assertEqual(mock_stderr.getvalue(), "")
+
+    @patch("sys.stdout", new_callable=StringIO)
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_set_log_level_manually(self, mock_stderr, mock_stdout):
+        logger = logging.getLogger("test.logger")
+
+        # Changing log level after `bootstrap.setup_logging`
+        logging.getLogger().setLevel(logging.CRITICAL)
+
+        logger.debug("debug message")
+        logger.info("info message")
+        logger.warning("warning message")
+        logger.error("error message")
+        logger.critical("critical message")
+
+        data = json.loads(mock_stdout.getvalue())
+        data.pop("timestamp")
+
+        self.assertEqual(
+            data,
+            {
+                "level": "CRITICAL",
+                "logger": "test.logger",
+                "message": "critical message",
+                "requestId": "",
+            },
+        )
+        self.assertEqual(mock_stderr.getvalue(), "")
+
+    @patch("sys.stdout", new_callable=StringIO)
+    @patch("sys.stderr", new_callable=StringIO)
+    def test_set_log_level_with_dictConfig(self, mock_stderr, mock_stdout):
+        # Changing log level after `bootstrap.setup_logging`
+        logging.config.dictConfig(
+            {
+                "version": 1,
+                "disable_existing_loggers": False,
+                "formatters": {"simple": {"format": "%(levelname)-8s - %(message)s"}},
+                "handlers": {
+                    "stdout": {
+                        "class": "logging.StreamHandler",
+                        "formatter": "simple",
+                    },
+                },
+                "root": {
+                    "level": "CRITICAL",
+                    "handlers": [
+                        "stdout",
+                    ],
+                },
+            }
+        )
+
+        logger = logging.getLogger("test.logger")
+        logger.debug("debug message")
+        logger.info("info message")
+        logger.warning("warning message")
+        logger.error("error message")
+        logger.critical("critical message")
+
+        data = mock_stderr.getvalue()
+        self.assertEqual(
+            data,
+            "CRITICAL - critical message\n",
+        )
+        self.assertEqual(mock_stdout.getvalue(), "")
+
+
 class TestBootstrapModule(unittest.TestCase):
-    @patch("awslambdaric.bootstrap.handle_event_request")
     @patch("awslambdaric.bootstrap.LambdaRuntimeClient")
-    def test_run(self, mock_runtime_client, mock_handle_event_request):
+    def test_run(self, mock_runtime_client):
         expected_app_root = "/tmp/test/app_root"
         expected_handler = "app.my_test_handler"
         expected_lambda_runtime_api_addr = "test_addr"
@@ -1140,22 +1510,22 @@ def test_run(self, mock_runtime_client, mock_handle_event_request):
             MagicMock(),
         ]
 
-        with self.assertRaises(TypeError):
+        with self.assertRaises(SystemExit) as cm:
             bootstrap.run(
                 expected_app_root, expected_handler, expected_lambda_runtime_api_addr
             )
 
-        mock_handle_event_request.assert_called_once()
+        self.assertEqual(cm.exception.code, 1)
 
     @patch(
         "awslambdaric.bootstrap.LambdaLoggerHandler",
         Mock(side_effect=Exception("Boom!")),
     )
-    @patch("awslambdaric.bootstrap.build_fault_result", MagicMock())
+    @patch("awslambdaric.bootstrap.build_fault_result")
     @patch("awslambdaric.bootstrap.log_error", MagicMock())
     @patch("awslambdaric.bootstrap.LambdaRuntimeClient", MagicMock())
     @patch("awslambdaric.bootstrap.sys")
-    def test_run_exception(self, mock_sys):
+    def test_run_exception(self, mock_sys, mock_build_fault_result):
         class TestException(Exception):
             pass
 
@@ -1163,6 +1533,7 @@ class TestException(Exception):
         expected_handler = "app.my_test_handler"
         expected_lambda_runtime_api_addr = "test_addr"
 
+        mock_build_fault_result.return_value = {}
         mock_sys.exit.side_effect = TestException("Boom!")
 
         with self.assertRaises(TestException):
@@ -1173,5 +1544,53 @@ class TestException(Exception):
         mock_sys.exit.assert_called_once_with(1)
 
 
+class TestOnInitComplete(unittest.TestCase):
+    def tearDown(self):
+        # We are accessing private filed for cleaning up
+        snapshot_restore_py._before_snapshot_registry = []
+        snapshot_restore_py._after_restore_registry = []
+
+    # We are using ANY over here as the main thing we want to test is teh errorType propogation and stack trace generation
+    error_result = {
+        "errorMessage": "This is a Dummy type error",
+        "errorType": "TypeError",
+        "requestId": "",
+        "stackTrace": ANY,
+    }
+
+    def raise_type_error(self):
+        raise TypeError("This is a Dummy type error")
+
+    @patch("awslambdaric.bootstrap.LambdaRuntimeClient")
+    def test_before_snapshot_exception(self, mock_runtime_client):
+        snapshot_restore_py.register_before_snapshot(self.raise_type_error)
+
+        with self.assertRaises(SystemExit) as cm:
+            bootstrap.on_init_complete(
+                mock_runtime_client, log_sink=bootstrap.StandardLogSink()
+            )
+
+        self.assertEqual(cm.exception.code, 64)
+        mock_runtime_client.post_init_error.assert_called_once_with(
+            self.error_result,
+            FaultException.BEFORE_SNAPSHOT_ERROR,
+        )
+
+    @patch("awslambdaric.bootstrap.LambdaRuntimeClient")
+    def test_after_restore_exception(self, mock_runtime_client):
+        snapshot_restore_py.register_after_restore(self.raise_type_error)
+
+        with self.assertRaises(SystemExit) as cm:
+            bootstrap.on_init_complete(
+                mock_runtime_client, log_sink=bootstrap.StandardLogSink()
+            )
+
+        self.assertEqual(cm.exception.code, 65)
+        mock_runtime_client.restore_next.assert_called_once()
+        mock_runtime_client.report_restore_error.assert_called_once_with(
+            self.error_result
+        )
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_lambda_context.py b/tests/test_lambda_context.py
index 545efa1..f7959ab 100644
--- a/tests/test_lambda_context.py
+++ b/tests/test_lambda_context.py
@@ -4,7 +4,7 @@
 
 import os
 import unittest
-from unittest.mock import patch, MagicMock
+from unittest.mock import MagicMock, patch
 
 from awslambdaric.lambda_context import LambdaContext
 
@@ -37,6 +37,7 @@ def test_init(self):
         self.assertEqual(context.memory_limit_in_mb, "1234")
         self.assertEqual(context.function_version, "version1")
         self.assertEqual(context.invoked_function_arn, "arn:test1")
+        self.assertEqual(context.tenant_id, None)
         self.assertEqual(context.identity.cognito_identity_id, None)
         self.assertEqual(context.identity.cognito_identity_pool_id, None)
         self.assertEqual(context.client_context.client.installation_id, None)
@@ -74,6 +75,21 @@ def test_init_cognito(self):
         self.assertEqual(context.identity.cognito_identity_id, "id1")
         self.assertEqual(context.identity.cognito_identity_pool_id, "poolid1")
 
+    def test_init_tenant_id(self):
+        client_context = {}
+        cognito_identity = {}
+        tenant_id = "blue"
+
+        context = LambdaContext(
+            "invoke-id1",
+            client_context,
+            cognito_identity,
+            1415836801000,
+            "arn:test",
+            tenant_id,
+        )
+        self.assertEqual(context.tenant_id, "blue")
+
     def test_init_client_context(self):
         client_context = {
             "client": {
diff --git a/tests/test_lambda_runtime_client.py b/tests/test_lambda_runtime_client.py
index 7b1bf66..fc4af65 100644
--- a/tests/test_lambda_runtime_client.py
+++ b/tests/test_lambda_runtime_client.py
@@ -7,11 +7,14 @@
 import unittest.mock
 from unittest.mock import MagicMock, patch
 
+from awslambdaric import __version__
 from awslambdaric.lambda_runtime_client import (
+    InvocationRequest,
     LambdaRuntimeClient,
     LambdaRuntimeClientError,
-    InvocationRequest,
+    _user_agent,
 )
+from awslambdaric.lambda_runtime_marshaller import to_json
 
 
 class TestInvocationRequest(unittest.TestCase):
@@ -23,6 +26,7 @@ def test_constructor(self):
             deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
             client_context="Lambda-Runtime-Client-Context",
             cognito_identity="Lambda-Runtime-Cognito-Identity",
+            tenant_id="Lambda-Runtime-Aws-Tenant-Id",
             content_type="Content-Type",
             event_body="response_body",
         )
@@ -34,6 +38,7 @@ def test_constructor(self):
             deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
             client_context="Lambda-Runtime-Client-Context",
             cognito_identity="Lambda-Runtime-Cognito-Identity",
+            tenant_id="Lambda-Runtime-Aws-Tenant-Id",
             content_type="Content-Type",
             event_body="response_body",
         )
@@ -45,6 +50,7 @@ def test_constructor(self):
             deadline_time_in_ms="Lambda-Runtime-Deadline-Ms",
             client_context="Lambda-Runtime-Client-Context",
             cognito_identity="Lambda-Runtime-Cognito-Identity",
+            tenant_id="Lambda-Runtime-Aws-Tenant-Id",
             content_type="Content-Type",
             event_body="another_response_body",
         )
@@ -65,6 +71,7 @@ def test_wait_next_invocation(self, mock_runtime_client):
             "Lambda-Runtime-Deadline-Ms": 12,
             "Lambda-Runtime-Client-Context": "client_context",
             "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+            "Lambda-Runtime-Aws-Tenant-Id": "tenant_id",
             "Content-Type": "application/json",
         }
         mock_runtime_client.next.return_value = response_body, headears
@@ -79,9 +86,117 @@ def test_wait_next_invocation(self, mock_runtime_client):
         self.assertEqual(event_request.deadline_time_in_ms, 12)
         self.assertEqual(event_request.client_context, "client_context")
         self.assertEqual(event_request.cognito_identity, "cognito_identity")
+        self.assertEqual(event_request.tenant_id, "tenant_id")
+        self.assertEqual(event_request.content_type, "application/json")
+        self.assertEqual(event_request.event_body, response_body)
+
+        # Using ThreadPoolExecutor to polling next()
+        runtime_client = LambdaRuntimeClient("localhost:1234", True)
+
+        event_request = runtime_client.wait_next_invocation()
+
+        self.assertIsNotNone(event_request)
+        self.assertEqual(event_request.invoke_id, "RID1234")
+        self.assertEqual(event_request.x_amzn_trace_id, "TID1234")
+        self.assertEqual(event_request.invoked_function_arn, "FARN1234")
+        self.assertEqual(event_request.deadline_time_in_ms, 12)
+        self.assertEqual(event_request.client_context, "client_context")
+        self.assertEqual(event_request.cognito_identity, "cognito_identity")
+        self.assertEqual(event_request.tenant_id, "tenant_id")
         self.assertEqual(event_request.content_type, "application/json")
         self.assertEqual(event_request.event_body, response_body)
 
+    @patch("awslambdaric.lambda_runtime_client.runtime_client")
+    def test_wait_next_invocation_without_tenant_id_header(self, mock_runtime_client):
+        response_body = b"{}"
+        headers = {
+            "Lambda-Runtime-Aws-Request-Id": "RID1234",
+            "Lambda-Runtime-Trace-Id": "TID1234",
+            "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+            "Lambda-Runtime-Deadline-Ms": 12,
+            "Lambda-Runtime-Client-Context": "client_context",
+            "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+            "Content-Type": "application/json",
+        }
+        mock_runtime_client.next.return_value = response_body, headers
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+
+        event_request = runtime_client.wait_next_invocation()
+
+        self.assertIsNotNone(event_request)
+        self.assertIsNone(event_request.tenant_id)
+        self.assertEqual(event_request.event_body, response_body)
+
+    @patch("awslambdaric.lambda_runtime_client.runtime_client")
+    def test_wait_next_invocation_with_null_tenant_id_header(self, mock_runtime_client):
+        response_body = b"{}"
+        headers = {
+            "Lambda-Runtime-Aws-Request-Id": "RID1234",
+            "Lambda-Runtime-Trace-Id": "TID1234",
+            "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+            "Lambda-Runtime-Deadline-Ms": 12,
+            "Lambda-Runtime-Client-Context": "client_context",
+            "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+            "Lambda-Runtime-Aws-Tenant-Id": None,
+            "Content-Type": "application/json",
+        }
+        mock_runtime_client.next.return_value = response_body, headers
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+
+        event_request = runtime_client.wait_next_invocation()
+
+        self.assertIsNotNone(event_request)
+        self.assertIsNone(event_request.tenant_id)
+        self.assertEqual(event_request.event_body, response_body)
+
+    @patch("awslambdaric.lambda_runtime_client.runtime_client")
+    def test_wait_next_invocation_with_empty_tenant_id_header(
+        self, mock_runtime_client
+    ):
+        response_body = b"{}"
+        headers = {
+            "Lambda-Runtime-Aws-Request-Id": "RID1234",
+            "Lambda-Runtime-Trace-Id": "TID1234",
+            "Lambda-Runtime-Invoked-Function-Arn": "FARN1234",
+            "Lambda-Runtime-Deadline-Ms": 12,
+            "Lambda-Runtime-Client-Context": "client_context",
+            "Lambda-Runtime-Cognito-Identity": "cognito_identity",
+            "Lambda-Runtime-Aws-Tenant-Id": "",
+            "Content-Type": "application/json",
+        }
+        mock_runtime_client.next.return_value = response_body, headers
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+
+        event_request = runtime_client.wait_next_invocation()
+
+        self.assertIsNotNone(event_request)
+        self.assertEqual(event_request.tenant_id, "")
+        self.assertEqual(event_request.event_body, response_body)
+
+    error_result = {
+        "errorMessage": "Dummy message",
+        "errorType": "Runtime.DummyError",
+        "requestId": "",
+        "stackTrace": [],
+    }
+
+    headers = {"Lambda-Runtime-Function-Error-Type": error_result["errorType"]}
+
+    restore_error_result = {
+        "errorMessage": "Dummy Restore error",
+        "errorType": "Runtime.DummyRestoreError",
+        "requestId": "",
+        "stackTrace": [],
+    }
+
+    restore_error_header = {
+        "Lambda-Runtime-Function-Error-Type": "Runtime.AfterRestoreError"
+    }
+
+    before_snapshot_error_header = {
+        "Lambda-Runtime-Function-Error-Type": "Runtime.BeforeSnapshotError"
+    }
+
     @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
     def test_post_init_error(self, MockHTTPConnection):
         mock_conn = MockHTTPConnection.return_value
@@ -91,11 +206,14 @@ def test_post_init_error(self, MockHTTPConnection):
         mock_response.code = http.HTTPStatus.ACCEPTED
 
         runtime_client = LambdaRuntimeClient("localhost:1234")
-        runtime_client.post_init_error("error_data")
+        runtime_client.post_init_error(self.error_result)
 
         MockHTTPConnection.assert_called_with("localhost:1234")
         mock_conn.request.assert_called_once_with(
-            "POST", "/2018-06-01/runtime/init/error", "error_data"
+            "POST",
+            "/2018-06-01/runtime/init/error",
+            to_json(self.error_result),
+            headers=self.headers,
         )
         mock_response.read.assert_called_once()
 
@@ -110,7 +228,7 @@ def test_post_init_error_non_accepted_status_code(self, MockHTTPConnection):
         runtime_client = LambdaRuntimeClient("localhost:1234")
 
         with self.assertRaises(LambdaRuntimeClientError) as cm:
-            runtime_client.post_init_error("error_data")
+            runtime_client.post_init_error(self.error_result)
         returned_exception = cm.exception
 
         self.assertEqual(returned_exception.endpoint, "/2018-06-01/runtime/init/error")
@@ -195,15 +313,76 @@ def test_post_invocation_error_with_too_large_xray_cause(self, mock_runtime_clie
             invoke_id, error_data, ""
         )
 
+    @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+    def test_restore_next(self, MockHTTPConnection):
+        mock_conn = MockHTTPConnection.return_value
+        mock_response = MagicMock(autospec=http.client.HTTPResponse)
+        mock_conn.getresponse.return_value = mock_response
+        mock_response.read.return_value = b""
+        mock_response.code = http.HTTPStatus.OK
+
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+        runtime_client.restore_next()
+
+        MockHTTPConnection.assert_called_with("localhost:1234")
+        mock_conn.request.assert_called_once_with(
+            "GET",
+            "/2018-06-01/runtime/restore/next",
+        )
+        mock_response.read.assert_called_once()
+
+    @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+    def test_restore_error(self, MockHTTPConnection):
+        mock_conn = MockHTTPConnection.return_value
+        mock_response = MagicMock(autospec=http.client.HTTPResponse)
+        mock_conn.getresponse.return_value = mock_response
+        mock_response.read.return_value = b""
+        mock_response.code = http.HTTPStatus.ACCEPTED
+
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+        runtime_client.report_restore_error(self.restore_error_result)
+
+        MockHTTPConnection.assert_called_with("localhost:1234")
+        mock_conn.request.assert_called_once_with(
+            "POST",
+            "/2018-06-01/runtime/restore/error",
+            to_json(self.restore_error_result),
+            headers=self.restore_error_header,
+        )
+        mock_response.read.assert_called_once()
+
+    @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection)
+    def test_init_before_snapshot_error(self, MockHTTPConnection):
+        mock_conn = MockHTTPConnection.return_value
+        mock_response = MagicMock(autospec=http.client.HTTPResponse)
+        mock_conn.getresponse.return_value = mock_response
+        mock_response.read.return_value = b""
+        mock_response.code = http.HTTPStatus.ACCEPTED
+
+        runtime_client = LambdaRuntimeClient("localhost:1234")
+        runtime_client.post_init_error(self.error_result, "Runtime.BeforeSnapshotError")
+
+        MockHTTPConnection.assert_called_with("localhost:1234")
+        mock_conn.request.assert_called_once_with(
+            "POST",
+            "/2018-06-01/runtime/init/error",
+            to_json(self.error_result),
+            headers=self.before_snapshot_error_header,
+        )
+        mock_response.read.assert_called_once()
+
     def test_connection_refused(self):
         with self.assertRaises(ConnectionRefusedError):
             runtime_client = LambdaRuntimeClient("127.0.0.1:1")
-            runtime_client.post_init_error("error")
+            runtime_client.post_init_error(self.error_result)
 
     def test_invalid_addr(self):
         with self.assertRaises(OSError):
             runtime_client = LambdaRuntimeClient("::::")
-            runtime_client.post_init_error("error")
+            runtime_client.post_init_error(self.error_result)
+
+    def test_lambdaric_version(self):
+        self.assertTrue(_user_agent().endswith(__version__))
 
 
 class TestLambdaRuntimeClientError(unittest.TestCase):
diff --git a/tests/test_lambda_runtime_marshaller.py b/tests/test_lambda_runtime_marshaller.py
index 8268de1..843bcee 100644
--- a/tests/test_lambda_runtime_marshaller.py
+++ b/tests/test_lambda_runtime_marshaller.py
@@ -3,12 +3,39 @@
 """
 
 import decimal
+import os
 import unittest
-
+from parameterized import parameterized
 from awslambdaric.lambda_runtime_marshaller import to_json
 
 
 class TestLambdaRuntimeMarshaller(unittest.TestCase):
+    execution_envs = (
+        "AWS_Lambda_python3.13",
+        "AWS_Lambda_python3.12",
+        "AWS_Lambda_python3.11",
+        "AWS_Lambda_python3.10",
+        "AWS_Lambda_python3.9",
+    )
+
+    envs_lambda_marshaller_ensure_ascii_false = {
+        "AWS_Lambda_python3.12",
+        "AWS_Lambda_python3.13",
+    }
+
+    execution_envs_lambda_marshaller_ensure_ascii_true = tuple(
+        set(execution_envs).difference(envs_lambda_marshaller_ensure_ascii_false)
+    )
+    execution_envs_lambda_marshaller_ensure_ascii_false = tuple(
+        envs_lambda_marshaller_ensure_ascii_false
+    )
+
+    def setUp(self):
+        self.org_os_environ = os.environ
+
+    def tearDown(self):
+        os.environ = self.org_os_environ
+
     def test_to_json_decimal_encoding(self):
         response = to_json({"pi": decimal.Decimal("3.14159")})
         self.assertEqual('{"pi": 3.14159}', response)
@@ -37,3 +64,23 @@ def test_json_serializer_is_not_default_json(self):
         self.assertTrue(hasattr(internal_json, "YOLO"))
         self.assertFalse(hasattr(stock_json, "YOLO"))
         self.assertTrue(hasattr(simplejson, "YOLO"))
+
+    @parameterized.expand(execution_envs_lambda_marshaller_ensure_ascii_false)
+    def test_to_json_unicode_not_escaped_encoding(self, execution_env):
+        os.environ = {"AWS_EXECUTION_ENV": execution_env}
+        response = to_json({"price": "£1.00"})
+        self.assertEqual('{"price": "£1.00"}', response)
+        self.assertNotEqual('{"price": "\\u00a31.00"}', response)
+        self.assertEqual(
+            19, len(response.encode("utf-8"))
+        )  # would be 23 bytes if a unicode escape was returned
+
+    @parameterized.expand(execution_envs_lambda_marshaller_ensure_ascii_true)
+    def test_to_json_unicode_is_escaped_encoding(self, execution_env):
+        os.environ = {"AWS_EXECUTION_ENV": execution_env}
+        response = to_json({"price": "£1.00"})
+        self.assertEqual('{"price": "\\u00a31.00"}', response)
+        self.assertNotEqual('{"price": "£1.00"}', response)
+        self.assertEqual(
+            23, len(response.encode("utf-8"))
+        )  # would be 19 bytes if a escaped was returned
diff --git a/tests/test_runtime_hooks.py b/tests/test_runtime_hooks.py
new file mode 100644
index 0000000..e73204f
--- /dev/null
+++ b/tests/test_runtime_hooks.py
@@ -0,0 +1,65 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+import unittest
+from unittest.mock import patch, call
+from awslambdaric import lambda_runtime_hooks_runner
+import snapshot_restore_py
+
+
+def fun_test1():
+    print("In function ONE")
+
+
+def fun_test2():
+    print("In function TWO")
+
+
+def fun_with_args_kwargs(x, y, **kwargs):
+    print("Here are the args:", x, y)
+    print("Here are the keyword args:", kwargs)
+
+
+class TestRuntimeHooks(unittest.TestCase):
+    def tearDown(self):
+        # We are accessing private filed for cleaning up
+        snapshot_restore_py._before_snapshot_registry = []
+        snapshot_restore_py._after_restore_registry = []
+
+    @patch("builtins.print")
+    def test_before_snapshot_execution_order(self, mock_print):
+        snapshot_restore_py.register_before_snapshot(
+            fun_with_args_kwargs, 5, 7, arg1="Lambda", arg2="SnapStart"
+        )
+        snapshot_restore_py.register_before_snapshot(fun_test2)
+        snapshot_restore_py.register_before_snapshot(fun_test1)
+
+        lambda_runtime_hooks_runner.run_before_snapshot()
+
+        calls = []
+        calls.append(call("In function ONE"))
+        calls.append(call("In function TWO"))
+        calls.append(call("Here are the args:", 5, 7))
+        calls.append(
+            call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"})
+        )
+        self.assertEqual(calls, mock_print.mock_calls)
+
+    @patch("builtins.print")
+    def test_after_restore_execution_order(self, mock_print):
+        snapshot_restore_py.register_after_restore(
+            fun_with_args_kwargs, 11, 13, arg1="Lambda", arg2="SnapStart"
+        )
+        snapshot_restore_py.register_after_restore(fun_test2)
+        snapshot_restore_py.register_after_restore(fun_test1)
+
+        lambda_runtime_hooks_runner.run_after_restore()
+
+        calls = []
+        calls.append(call("Here are the args:", 11, 13))
+        calls.append(
+            call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"})
+        )
+        calls.append(call("In function TWO"))
+        calls.append(call("In function ONE"))
+        self.assertEqual(calls, mock_print.mock_calls)