Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement verbose error page mode #368

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ For the 2.x changelog see the [viur/server](https://github.com/viur-framework/se
- "viur.db.engine" config variable to inject different database drivers
- Global jinja2 function "translate" for instances where compile-time resolving is not possible
- Passing getEmptyValue() in the structure definition in json-render
- A verbose error handler output, controllable by the "viur.debug.verboseErrorHandler" config variable.

### Changed
- Replaced viur.core.db with a shim around viur-datastore
Expand Down
2 changes: 2 additions & 0 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"viur.debug.traceInternalCallRouting": False,
# If enabled, we log all datastore queries performed
"viur.debug.traceQueries": False,
# If enabled, ViUR provides - in case of an error - several debug/meta information of the request
"viur.debug.verboseErrorHandler": False,

# Unless overridden by the Project: Use english as default language
"viur.defaultLanguage": "en",
Expand Down
128 changes: 83 additions & 45 deletions core/request.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import traceback, os, unicodedata, typing
from viur.core.config import conf
from urllib import parse
from string import Template
from io import StringIO
import webob
from viur.core import errors
from urllib.parse import urljoin, urlparse, unquote
from viur.core.logging import requestLogger, client as loggingClient, requestLoggingRessource
from viur.core import utils, db
from viur.core.utils import currentSession, currentLanguage
import logging
from time import time
import os
import sys
import traceback
import typing
import unicodedata
from abc import ABC, abstractmethod
from io import StringIO
from string import Template
from time import time
from urllib import parse
from urllib.parse import unquote, urljoin, urlparse

import webob

from viur.core import db, errors, utils
from viur.core.config import conf
from viur.core.logging import client as loggingClient, requestLogger, requestLoggingRessource
from viur.core.utils import currentLanguage, currentSession

"""
This module implements the WSGI (Web Server Gateway Interface) layer for ViUR. This is the main entry
Expand All @@ -22,6 +26,7 @@
request processing (useful for global ratelimiting, DDoS prevention or access control).
"""


class RequestValidator(ABC):
"""
RequestValidators can be used to validate a request very early on. If the validate method returns a tuple,
Expand Down Expand Up @@ -310,48 +315,81 @@ def processRequest(self):
except Exception as e:
logging.exception(e)
raise
except errors.HTTPException as e:
if conf["viur.debug.traceExceptions"]:
raise
self.response.body = b""
self.response.status = '%d %s' % (e.status, e.name)
res = None
if conf["viur.errorHandler"]:
try:
res = conf["viur.errorHandler"](e)
except Exception as newE:
logging.error("viur.errorHandler failed!")
logging.exception(newE)
res = None
if not res:
tpl = Template(open(conf["viur.errorTemplate"], "r").read())
res = tpl.safe_substitute({"error_code": e.status, "error_name": e.name, "error_descr": e.descr})
self.response.write(res.encode("UTF-8"))
except Exception as e: # Something got really wrong
logging.error("Viur caught an unhandled exception!")
logging.exception(e)
except Exception as e:
if isinstance(e, errors.HTTPException):
if conf["viur.debug.traceExceptions"]:
raise
errorCode = e.status
errorName = e.name
errorDescr = e.descr
else:
# Something got really wrong
logging.error("Viur caught an unhandled exception!")
logging.exception(e)
errorCode = 500
errorName = "Internal Server Error"
errorDescr = "The server encountered an unexpected error and is unable to process your request."

self.response.body = b""
self.response.status = 500
self.response.status = f"{errorCode} {errorName}"

res = None
if conf["viur.errorHandler"]:
try:
res = conf["viur.errorHandler"](e)
except Exception as newE:
logging.error("viur.errorHandler failed!")
logging.exception(newE)
res = None

if not res:
tpl = Template(open(conf["viur.errorTemplate"], "r").read())
descr = "The server encountered an unexpected error and is unable to process your request."
if self.isDevServer: # Were running on development Server
strIO = StringIO()
traceback.print_exc(file=strIO)
descr = strIO.getvalue()
descr = descr.replace("<", "&lt;").replace(">", "&gt;").replace(" ", "&nbsp;").replace("\n",
"<br />")
res = tpl.safe_substitute(
{"error_code": "500", "error_name": "Internal Server Error", "error_descr": descr})
with open(conf["viur.errorTemplate"], "r") as fh:
tpl = Template(fh.read())
debugInformation = ""
# We're running on development server or verbose mode is on
if self.isDevServer or conf["viur.debug.verboseErrorHandler"]:
if not isinstance(e, errors.HTTPException):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viur-framework/developers What do your think about this. Shall we print a traceback on HTTPExceptions or not? Might be helpful to find the right case, if there are multiple reasons.

# No traceback on HTTPException, use conf["viur.debug.traceExceptions"] for debugging
strIO = StringIO()
traceback.print_exc(file=strIO)
errorDescr = strIO.getvalue() \
.replace("<", "&lt;").replace(">", "&gt;") \
.replace(" ", "&nbsp;").replace("\n", "<br />")
params = "\n".join(
f'<tr><td>{key}</td><td><pre>{value!r}</pre></td></tr>'
for key, value in self.request.params.items()
)
try:
currentUser = utils.getCurrentUser()
except Exception: # noqa
currentUser = None
debugInformation = [
("Debug Information", ""), # header
# Request data
("Request Method", self.request.method),
("Request URL ", self.request.url),
("Request Path (post-processed)", self.request.path),
("Request Params", f"<table><tr><th>Key</th><th>Value</th></tr>{params}</table>"),
# Session related data
("User", currentUser and currentUser["name"]),
("Language", currentLanguage.get()),
# Environment
("Python Version", sys.version),
("ViUR-Core Version", ".".join(map(str, conf["viur.version"]))),
# ("ViUR-Datastore Version", db.__version__), # TODO: N/A in datastore
]
debugInformation = "\n".join(
f'<div>{label}</div><div>{value}</div>'
for label, value in debugInformation
)

res = tpl.safe_substitute({
"error_code": errorCode,
"error_name": errorName,
"error_descr": errorDescr,
"debug_information": debugInformation,
})
self.response.write(res.encode("UTF-8"))

finally:
self.saveSession()
SEVERITY = "DEBUG"
Expand Down Expand Up @@ -466,7 +504,7 @@ def processTypeHint(self, typeHint: typing.ClassVar, inValue: typing.Union[str,
return "False", False
raise ValueError("TypeHint %s not supported" % typeHint)

def findAndCall(self, path:str, *args, **kwargs):
def findAndCall(self, path: str, *args, **kwargs):
"""
Does the actual work of sanitizing the parameter, determine which @exposed (or @internalExposed) function
to call (and with witch parameters)
Expand Down
Loading