Repository: bentoml/BentoML

Issue:
Prediction log in AWS Lambda deployment
**Is your feature request related to a problem? Please describe.**
Currently, a BentoService AWS Lambda deployment only logs the prediction request but not the response (a.k.a the output of the model.) of API calls.


**Describe the solution you'd like**
Print the output of the model at the end of the lambda callback function 



Discussion:

Resolving patch:
diff --git a/bentoml/yatai/deployment/aws_lambda/lambda_app.py b/bentoml/yatai/deployment/aws_lambda/lambda_app.py
--- a/bentoml/yatai/deployment/aws_lambda/lambda_app.py
+++ b/bentoml/yatai/deployment/aws_lambda/lambda_app.py
@@ -14,6 +14,8 @@
 
 import os
 import sys
+import logging
+import json
 
 try:
     import download_extra_resources
@@ -38,6 +40,8 @@
 os.environ['BENTOML_HOME'] = '/tmp/bentoml/'
 from bentoml import load  # noqa
 
+logger = logging.getLogger('bentoml.lambda_app')
+
 bento_name = os.environ['BENTOML_BENTO_SERVICE_NAME']
 api_name = os.environ["BENTOML_API_NAME"]
 
@@ -45,11 +49,11 @@
 if not os.path.exists(bento_bundle_path):
     bento_bundle_path = os.path.join('/tmp/requirements', bento_name)
 
-print(f'Loading BentoService bundle from path: "{bento_bundle_path}"')
+logger.debug('Loading BentoService bundle from path: "%s"', bento_bundle_path)
 bento_service = load(bento_bundle_path)
-print(f'BentoService "{bento_service.name}" loaded successfully')
+logger.debug('BentoService "%s" loaded successfully', bento_service.name)
 bento_service_api = bento_service.get_service_api(api_name)
-print(f'BentoService API "{api_name}" loaded successfully')
+logger.debug('BentoService API "%s" loaded successfully', {api_name})
 
 this_module = sys.modules[__name__]
 
@@ -62,18 +66,34 @@ def api_func(event, context):  # pylint: disable=unused-argument
     Application Load Balancer, in which case the parameter `event` must be type `dict`
     containing the HTTP request headers and body.
 
-    see: https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html
+    You can find an example of which
+    variables you can expect from the `event` object on the AWS Docs, here
+    https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html
     """
 
     if type(event) is dict and "headers" in event and "body" in event:
-        return bento_service_api.handle_aws_lambda_event(event)
+        prediction = bento_service_api.handle_aws_lambda_event(event)
+        logger.info(
+            json.dumps(
+                {
+                    'event': event,
+                    'prediction': prediction["body"],
+                    'status_code': prediction["statusCode"],
+                }
+            )
+        )
+
+        if prediction["statusCode"] >= 400:
+            logger.warning('Error when predicting. Check logs for more information.')
+
+        return prediction
     else:
         error_msg = (
             'Error: Unexpected Lambda event data received. Currently BentoML lambda '
             'deployment can only handle event triggered by HTTP request from '
             'Application Load Balancer.'
         )
-        print(error_msg)
+        logger.error(error_msg)
         raise RuntimeError(error_msg)
 
 
diff --git a/bentoml/yatai/deployment/aws_lambda/operator.py b/bentoml/yatai/deployment/aws_lambda/operator.py
--- a/bentoml/yatai/deployment/aws_lambda/operator.py
+++ b/bentoml/yatai/deployment/aws_lambda/operator.py
@@ -189,8 +189,10 @@ def _deploy_lambda_function(
         for i in artifact_types
     ) and (py_major, py_minor) != ('3', '6'):
         raise BentoMLException(
-            'For Tensorflow and Keras model, only python3.6 is '
-            'supported for AWS Lambda deployment'
+            'AWS Lambda Deployment only supports BentoML services'
+            'built with Python 3.6.x. To fix this, repack your'
+            'service with the right Python version'
+            '(hint: pyenv/anaconda) and try again'
         )
 
     api_names = (
diff --git a/bentoml/yatai/deployment/aws_lambda/utils.py b/bentoml/yatai/deployment/aws_lambda/utils.py
--- a/bentoml/yatai/deployment/aws_lambda/utils.py
+++ b/bentoml/yatai/deployment/aws_lambda/utils.py
@@ -201,7 +201,7 @@ def init_sam_project(
     function_path = os.path.join(sam_project_path, deployment_name)
     os.mkdir(function_path)
     # Copy requirements.txt
-    logger.debug('Coping requirements.txt')
+    logger.debug('Copying requirements.txt')
     requirement_txt_path = os.path.join(bento_service_bundle_path, 'requirements.txt')
     shutil.copy(requirement_txt_path, function_path)
 
@@ -210,7 +210,7 @@ def init_sam_project(
     )
     if os.path.isdir(bundled_dep_path):
         # Copy bundled pip dependencies
-        logger.debug('Coping bundled_dependencies')
+        logger.debug('Copying bundled_dependencies')
         shutil.copytree(
             bundled_dep_path, os.path.join(function_path, 'bundled_pip_dependencies')
         )
@@ -232,7 +232,7 @@ def init_sam_project(
             requirement_file.writelines(required_modules)
 
     # Copy bento_service_model
-    logger.debug('Coping model directory')
+    logger.debug('Copying model directory')
     model_path = os.path.join(bento_service_bundle_path, bento_name)
     shutil.copytree(model_path, os.path.join(function_path, bento_name))
 


Test patch:
diff --git a/tests/deployment/aws_lambda/test_aws_lambda_deployment_operator.py b/tests/deployment/aws_lambda/test_aws_lambda_deployment_operator.py
--- a/tests/deployment/aws_lambda/test_aws_lambda_deployment_operator.py
+++ b/tests/deployment/aws_lambda/test_aws_lambda_deployment_operator.py
@@ -56,7 +56,7 @@ def generate_lambda_deployment_pb():
 
 def test_aws_lambda_app_py(monkeypatch):
     def test_predict(value):
-        return value['body']
+        return {'body': value['body'], 'statusCode': 200}
 
     class Mock_bento_service_class(object):
         name = "mock_bento_service"
@@ -106,7 +106,10 @@ def return_predict_func(mock_load_service):
     with pytest.raises(RuntimeError):
         predict("Invalid Input Type", None)
 
-    assert predict({"headers": [], "body": 'test'}, None) == 'test'
+    assert predict({"headers": [], "body": 'test'}, None) == {
+        'body': 'test',
+        'statusCode': 200,
+    }
 
 
 @patch('shutil.rmtree', MagicMock())