Skip to content

Commit

Permalink
Adds DEBUG to print Zappa errors in 500 messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich Jones committed Apr 1, 2016
1 parent d8c3486 commit c928afb
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 73 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ to change Zappa's behavior. Use these at your own risk!
{
"dev": {
"aws_region": "us-east-1", // AWS Region (default US East),
"debug": true // Print Zappa configuration errors tracebacks in the 500
"delete_zip": true // Delete the local zip archive after code updates
"domain": "yourapp.yourdomain.com", // Required if you're using a domain
"http_methods": ["GET", "POST"], // HTTP Methods to route,
"integration_response_codes": [200, 301, 404, 500], // Integration response status codes to route
Expand Down
3 changes: 2 additions & 1 deletion test_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"ttt333": {
"s3_bucket": "lmbda",
"app_function": "tests.test_app.hello_world",
"delete_zip": true
"delete_zip": true,
"debug": true
}
}
3 changes: 2 additions & 1 deletion test_settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
APP_MODULE = 'tests.test_app'
APP_FUNCTION = 'hello_world'
SCRIPT_NAME = 'hello_world'
DEBUG = 'True'
SCRIPT_NAME = 'hello_world'
6 changes: 6 additions & 0 deletions zappa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ZappaCLI(object):
api_stage = None
app_function = None
aws_region = None
debug = None
project_name = None
lambda_name = None
s3_bucket_name = None
Expand Down Expand Up @@ -283,6 +284,8 @@ def load_settings(self, settings_file="zappa_settings.json", session=None):
self.api_stage].get('app_function', None)
self.aws_region = self.zappa_settings[
self.api_stage].get('aws_region', 'us-east-1')
self.debug = self.zappa_settings[
self.api_stage].get('debug', True)

# Create an Zappa object..
self.zappa = Zappa(session)
Expand Down Expand Up @@ -326,6 +329,9 @@ def create_package(self):
app_module, app_function = self.app_function.rsplit('.', 1)
settings_s = "# Generated by Zappa\nAPP_MODULE='%s'\nAPP_FUNCTION='%s'\n" % (app_module, app_function)

if self.debug is not None:
settings_s = settings_s + "DEBUG='%s'" % (self.debug) # Cast to Bool in handler

# Lambda requires a specific chmod
temp_settings = tempfile.NamedTemporaryFile(delete=False)
os.chmod(temp_settings.name, 0644)
Expand Down
166 changes: 95 additions & 71 deletions zappa/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import importlib
import logging
import os
import traceback

from urllib import urlencode
from StringIO import StringIO
Expand All @@ -24,6 +25,9 @@
logger = logging.getLogger()
logger.setLevel(logging.INFO)

class LambdaException(Exception):
pass

class LambdaHandler(object):
"""
Singleton for avoiding duplicate setup.
Expand Down Expand Up @@ -58,80 +62,100 @@ def handler(self, event, context):
"""

time_start = datetime.datetime.now()

settings = self.settings

# The app module
app_module = importlib.import_module(settings.APP_MODULE)

# The application
app_function = getattr(app_module, settings.APP_FUNCTION)

app = ZappaWSGIMiddleware(app_function)

# This is a normal HTTP request
if event.get('method', None):
# If we just want to inspect this,
# return this event instead of processing the request
# https://your_api.aws-api.com/?event_echo=true
event_echo = getattr(settings, "EVENT_ECHO", True)
if event_echo:
if 'event_echo' in list(event['params'].values()):
return {'Content': str(event) + '\n' + str(context), 'Status': 200}

# Create the environment for WSGI and handle the request
environ = create_wsgi_request(event, script_name='',
trailing_slash=False)

# We are always on https on Lambda, so tell our wsgi app that.
environ['wsgi.url_scheme'] = 'https'

response = Response.from_app(app, environ)

zappa_returndict = dict()

if response.data:
zappa_returndict['Content'] = response.data

# Pack the WSGI response into our special dictionary.
for (header_name, header_value) in response.headers:
zappa_returndict[header_name] = header_value
zappa_returndict['Status'] = response.status_code

# To ensure correct status codes, we need to
# pack the response as a deterministic B64 string and raise it
# as an error to match our APIGW regex.
# The DOCTYPE ensures that the page still renders in the browser.
exception = None
if response.status_code in [400, 401, 403, 404, 500]:
content = "<!DOCTYPE html>" + str(response.status_code) + response.data
try:
time_start = datetime.datetime.now()

settings = self.settings

# The app module
app_module = importlib.import_module(settings.APP_MODULE)

# The application
app_function = getattr(app_module, settings.APP_FUNCTION)

app = ZappaWSGIMiddleware(app_function)

# This is a normal HTTP request
if event.get('method', None):
# If we just want to inspect this,
# return this event instead of processing the request
# https://your_api.aws-api.com/?event_echo=true
event_echo = getattr(settings, "EVENT_ECHO", True)
if event_echo:
if 'event_echo' in list(event['params'].values()):
return {'Content': str(event) + '\n' + str(context), 'Status': 200}

# Create the environment for WSGI and handle the request
environ = create_wsgi_request(event, script_name='',
trailing_slash=False)

# We are always on https on Lambda, so tell our wsgi app that.
environ['wsgi.url_scheme'] = 'https'

# Execute the application
response = Response.from_app(app, environ)

# This is the object we're going to return.
zappa_returndict = dict()

if response.data:
zappa_returndict['Content'] = response.data

# Pack the WSGI response into our special dictionary.
for (header_name, header_value) in response.headers:
zappa_returndict[header_name] = header_value
zappa_returndict['Status'] = response.status_code

# To ensure correct status codes, we need to
# pack the response as a deterministic B64 string and raise it
# as an error to match our APIGW regex.
# The DOCTYPE ensures that the page still renders in the browser.
exception = None
if response.status_code in [400, 401, 403, 404, 500]:
content = "<!DOCTYPE html>" + str(response.status_code) + response.data
exception = base64.b64encode(content)
# Internal are changed to become relative redirects
# so they still work for apps on raw APIGW and on a domain.
elif response.status_code in [301, 302]:
# Location is by default relative on Flask. Location is by default
# absolute on Werkzeug. We can set autocorrect_location_header on
# the response to False, but it doesn't work. We have to manually
# remove the host part.
location = response.location
hostname = 'https://' + environ['HTTP_HOST']
if location.startswith(hostname):
exception = location[len(hostname):]

# Calculate the total response time,
# and log it in the Common Log format.
time_end = datetime.datetime.now()
delta = time_end - time_start
response_time_ms = delta.total_seconds() * 1000
response.content = response.data
common_log(environ, response, response_time=response_time_ms)

# Finally, return the response to API Gateway.
if exception: # pragma: no cover
raise LambdaException(exception)
else:
return zappa_returndict
except LambdaException as e: # pragma: nocover
raise e
except Exception as e: # pragma: nocover

# Print statements are visible in the logs either way
print(e)

# Print the error to the browser upon failure?
debug = bool(getattr(app_module, settings.DEBUG, True))
if debug:
# Return this unspecified exception as a 500.
content = "<!DOCTYPE html>500. From Zappa: <pre>" + str(e) + "</pre><br /><pre>" + traceback.format_exc().replace('\n', '<br />') + "</pre>"
exception = base64.b64encode(content)
# Internal are changed to become relative redirects
# so they still work for apps on raw APIGW and on a domain.
elif response.status_code in [301, 302]:
# Location is by default relative on Flask. Location is by default
# absolute on Werkzeug. We can set autocorrect_location_header on
# the response to False, but it doesn't work. We have to manually
# remove the host part.
location = response.location
hostname = 'https://' + environ['HTTP_HOST']
if location.startswith(hostname):
exception = location[len(hostname):]

# Calculate the total response time,
# and log it in the Common Log format.
time_end = datetime.datetime.now()
delta = time_end - time_start
response_time_ms = delta.total_seconds() * 1000
response.content = response.data
common_log(environ, response, response_time=response_time_ms)

# Finally, return the response to API Gateway.
if exception: # pragma: no cover
raise Exception(exception)
else:
return zappa_returndict
raise e


def lambda_handler(event, context): # pragma: no cover
return LambdaHandler.lambda_handler(event, context)

0 comments on commit c928afb

Please sign in to comment.