Repository: getmoto/moto

Issue:
AWS Lambda error response is mixed up with docker container logs
Lambda function:

```python
def lambda_handler(event, context):
    raise RuntimeError("Oops")
```

The moto response is like:

```python
{'FunctionError': 'Handled',
 'Payload': <StreamingBody at 0x7f2e5c60cd20 for ClientResponseContentProxy at 0x7f2e5c57adc0>,
 'ResponseMetadata': {'HTTPHeaders': {'content-length': '748',
                                      'content-type': 'text/html; '
                                                      'charset=utf-8',
                                      'date': 'Mon, 07 Jun 2021 01:07:15 GMT',
                                      'server': 'Werkzeug/2.0.1 Python/3.7.10',
                                      'x-amz-crc32': '1107850810',
                                      'x-amz-function-error': 'Handled',
                                      'x-amzn-requestid': 'W2G2JRHZK5PB3NC151UWZFBCV27KHMZDMR6BILHQEPDCY6BGLBSY'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'W2G2JRHZK5PB3NC151UWZFBCV27KHMZDMR6BILHQEPDCY6BGLBSY',
                      'RetryAttempts': 0},
 'StatusCode': 200}
```

That looks correct (i.e. a 200 response with a  'FunctionError').  But the data is a mixture of logging and the error:

```
('error running lambda: lambda invoke failed output: \x1b[32mSTART RequestId: '
 'a62e5f44-8555-1644-d467-365ce24ec49e Version: 1\x1b[0m\n'
 "event: {'action': 'runtime-error'}\n"
 '[ERROR] RuntimeError: runtime-error\n'
 'Traceback (most recent call last):\n'
 '\xa0\xa0File "/var/task/lambda_function.py", line 12, in lambda_handler\n'
 '\xa0\xa0\xa0\xa0raise RuntimeError(action)\n'
 '\x1b[32mEND RequestId: a62e5f44-8555-1644-d467-365ce24ec49e\x1b[0m\n'
 '\x1b[32mREPORT RequestId: a62e5f44-8555-1644-d467-365ce24ec49e\tInit '
 'Duration: 160.93 ms\tDuration: 3.14 ms\tBilled Duration: 4 ms\tMemory Size: '
 '128 MB\tMax Memory Used: 23 MB\t\x1b[0m\n'
 '\n'
 '{"errorType":"RuntimeError","errorMessage":"runtime-error","stackTrace":["  '
 'File \\"/var/task/lambda_function.py\\", line 12, in lambda_handler\\n    '
 'raise RuntimeError(action)\\n"]}\n')
```

The moto error response is a string that cannot be parsed as JSON.

The actual AWS Lambda response for something like this is:

```python
{'ExecutedVersion': '$LATEST',
 'FunctionError': 'Unhandled',
 'Payload': <StreamingBody at 0x7faa8b9985f0 for ClientResponseContentProxy at 0x7faa8b9985a0>,
 'ResponseMetadata': {'HTTPHeaders': {'connection': 'keep-alive',
                                      'content-length': '178',
                                      'content-type': 'application/json',
                                      'date': 'Mon, 07 Jun 2021 01:00:50 GMT',
                                      'x-amz-executed-version': '$LATEST',
                                      'x-amz-function-error': 'Unhandled',
                                      'x-amzn-remapped-content-length': '0',
                                      'x-amzn-requestid': 'xxx-snipped',
                                      'x-amzn-trace-id': 'xxx-snipped'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'xxx-snipped',
                      'RetryAttempts': 0},
 'StatusCode': 200}
```

The AWS Lambda response has `application/json` content type and the error in the response body is like:

```python
{'errorMessage': 'Oops',
 'errorType': 'RuntimeError',
 'stackTrace': ['  File "/var/task/lambda_function.py", line 23, in '
                'lambda_handler\n'
                '    raise RuntimeError("Oops")\n']}
```

## versions

```
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.5 LTS"

$ docker --version
Docker version 20.10.7, build f0df350

$ python --version
Python 3.7.10

$ poetry show moto
name         : moto
version      : 1.3.16
description  : A library that allows your python tests to easily mock out the boto library

dependencies
 - aws-xray-sdk >=0.93,<0.96 || >0.96
 - boto >=2.36.0
 - boto3 >=1.9.201
 - botocore >=1.12.201
 - cfn-lint >=0.4.0
 - cryptography >=2.3.0
 - docker >=2.5.1
 - ecdsa <0.15
 - flask *
 - idna >=2.5,<3
 - Jinja2 >=2.10.1
 - jsondiff >=1.1.2
 - MarkupSafe <2.0
 - mock *
 - more-itertools *
 - python-dateutil >=2.1,<3.0.0
 - python-jose >=3.1.0,<4.0.0
 - pytz *
 - PyYAML >=5.1
 - requests >=2.5
 - responses >=0.9.0
 - six >1.9
 - sshpubkeys >=3.1.0
 - werkzeug *
 - xmltodict *
 - zipp *

```




Discussion:
Hi @dazza-codes, I'm pretty sure this has been improved/fixed since 1.3.16.
Are you able to upgrade to 2.x and verify this?

(Keep in mind that the installation method has changed since 2.x: https://github.com/spulec/moto/issues/3722)
That's good to hear.  Sorry, I try to avoid major.0.0 releases until they get to something like >= major.1.0 - I don't have a lot of time just now to check the change logs and try it out.  Congrats on the 2.x release.  My work-around for now is to also run live tests on a small AWS Lambda function.
> I try to avoid major.0.0 releases until they get to something like >= major.1.0

Understandable!

Just tested this out, actually, and this is still an issue. The PR I was thinking about only fixed the success response, which had the same issue. Marking this as a bug. 👍 

Resolving patch:
diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py
--- a/moto/awslambda/models.py
+++ b/moto/awslambda/models.py
@@ -626,24 +626,17 @@ def _invoke_lambda(self, code, event=None, context=None):
                 self.logs_group_name, log_stream_name, log_events, None
             )
 
-            if exit_code != 0:
-                raise Exception("lambda invoke failed output: {}".format(output))
-
             # We only care about the response from the lambda
             # Which is the last line of the output, according to https://github.com/lambci/docker-lambda/issues/25
             resp = output.splitlines()[-1]
             logs = os.linesep.join(
                 [line for line in self.convert(output).splitlines()[:-1]]
             )
-            return resp, False, logs
+            invocation_error = exit_code != 0
+            return resp, invocation_error, logs
         except docker.errors.DockerException as e:
             # Docker itself is probably not running - there will be no Lambda-logs to handle
             return "error running docker: {}".format(e), True, ""
-        except BaseException as e:
-            logs = os.linesep.join(
-                [line for line in self.convert(output).splitlines()[:-1]]
-            )
-            return "error running lambda: {}".format(e), True, logs
 
     def invoke(self, body, request_headers, response_headers):
 


Test patch:
diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py
--- a/tests/test_awslambda/test_lambda.py
+++ b/tests/test_awslambda/test_lambda.py
@@ -79,7 +79,7 @@ def lambda_handler(event, context):
     return _process_lambda(pfunc)
 
 
-def get_test_zip_file4():
+def get_test_zip_file_error():
     pfunc = """
 def lambda_handler(event, context):
     raise Exception('I failed!')
@@ -102,6 +102,37 @@ def test_list_functions():
     result["Functions"].should.have.length_of(0)
 
 
+@pytest.mark.network
+@mock_lambda
+def test_invoke_function_that_throws_error():
+    conn = boto3.client("lambda", _lambda_region)
+    conn.create_function(
+        FunctionName="testFunction",
+        Runtime="python2.7",
+        Role=get_role_name(),
+        Handler="lambda_function.lambda_handler",
+        Code={"ZipFile": get_test_zip_file_error()},
+    )
+
+    failure_response = conn.invoke(
+        FunctionName="testFunction", Payload=json.dumps({}), LogType="Tail"
+    )
+
+    failure_response.should.have.key("FunctionError").being.equal("Handled")
+
+    payload = failure_response["Payload"].read().decode("utf-8")
+    payload = json.loads(payload)
+    payload["errorType"].should.equal("Exception")
+    payload["errorMessage"].should.equal("I failed!")
+    payload.should.have.key("stackTrace")
+
+    logs = base64.b64decode(failure_response["LogResult"]).decode("utf-8")
+    logs.should.contain("START RequestId:")
+    logs.should.contain("I failed!: Exception")
+    logs.should.contain("Traceback (most recent call last):")
+    logs.should.contain("END RequestId:")
+
+
 @pytest.mark.network
 @pytest.mark.parametrize("invocation_type", [None, "RequestResponse"])
 @mock_lambda
@@ -1390,7 +1421,7 @@ def test_invoke_function_from_sqs_exception():
         Runtime="python2.7",
         Role=get_role_name(),
         Handler="lambda_function.lambda_handler",
-        Code={"ZipFile": get_test_zip_file4()},
+        Code={"ZipFile": get_test_zip_file_error()},
         Description="test lambda function",
         Timeout=3,
         MemorySize=128,