mirror of https://github.com/nucypher/nucypher.git
Initial work to return more comprehensive error information when there is a WorkerPool exception.
parent
77361e42ab
commit
154a4ac47b
|
@ -514,6 +514,7 @@ Some common returned status codes you may encounter are:
|
|||
- ``400 BAD REQUEST`` -- The server cannot or will not process the request due to something that is perceived to
|
||||
be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
|
||||
- ``401 UNAUTHORIZED`` -- Authentication is required and the request has failed to provide valid authentication credentials.
|
||||
- ``404 NOT FOUND`` -- Request could not be completed because requested resources could not be found.
|
||||
- ``500 INTERNAL SERVER ERROR`` -- The server encountered an unexpected condition that prevented it from
|
||||
fulfilling the request.
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from hendrix.deploy.base import HendrixDeploy
|
|||
from hendrix.deploy.tls import HendrixDeployTLS
|
||||
from twisted.internet import reactor, stdio
|
||||
|
||||
from nucypher.cli.processes import JSONRPCLineReceiver
|
||||
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
|
||||
from nucypher.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter, WebEmitter
|
||||
from nucypher.control.interfaces import ControlInterface
|
||||
|
@ -250,6 +251,7 @@ class WebController(InterfaceControlServer):
|
|||
|
||||
_captured_status_codes = {200: 'OK',
|
||||
400: 'BAD REQUEST',
|
||||
404: 'NOT FOUND',
|
||||
500: 'INTERNAL SERVER ERROR'}
|
||||
|
||||
def test_client(self):
|
||||
|
@ -302,8 +304,27 @@ class WebController(InterfaceControlServer):
|
|||
def __call__(self, *args, **kwargs):
|
||||
return self.handle_request(*args, **kwargs)
|
||||
|
||||
def handle_request(self, method_name, control_request, *args, **kwargs) -> Response:
|
||||
@staticmethod
|
||||
def json_response_from_worker_pool_exception(exception):
|
||||
json_response = {}
|
||||
if isinstance(exception, WorkerPool.TimedOut):
|
||||
message_prefix = f"Execution timed out after {exception.timeout}s"
|
||||
else:
|
||||
message_prefix = f"Execution failed - no more values to try"
|
||||
json_response['failure_message'] = f"{message_prefix}; " \
|
||||
f"{len(exception.failures)} concurrent failures recorded"
|
||||
if exception.failures:
|
||||
failures = []
|
||||
for value, exc_info in exception.failures.items():
|
||||
failure = {'value': value}
|
||||
_, exception, tb = exc_info
|
||||
failure['error'] = str(exception)
|
||||
failures.append(failure)
|
||||
json_response['failures'] = failures
|
||||
|
||||
return json_response
|
||||
|
||||
def handle_request(self, method_name, control_request, *args, **kwargs) -> Response:
|
||||
_400_exceptions = (SpecificationError,
|
||||
TypeError,
|
||||
JSONDecodeError,
|
||||
|
@ -336,47 +357,26 @@ class WebController(InterfaceControlServer):
|
|||
error_message=WebController._captured_status_codes[__exception_code])
|
||||
|
||||
#
|
||||
# Server Errors
|
||||
# Execution Errors
|
||||
#
|
||||
except SpecificationError as e:
|
||||
__exception_code = 500
|
||||
except WorkerPoolException as e:
|
||||
# special case since WorkerPoolException contains multiple stack traces
|
||||
# - not ideal for returning from REST endpoints
|
||||
__exception_code = 404
|
||||
if self.crash_on_error:
|
||||
raise
|
||||
return self.emitter.exception(
|
||||
e=e,
|
||||
log_level='critical',
|
||||
response_code=__exception_code,
|
||||
error_message=WebController._captured_status_codes[__exception_code])
|
||||
|
||||
json_response_from_exception = self.json_response_from_worker_pool_exception(e)
|
||||
return self.emitter.exception_with_response(
|
||||
json_error_response=json_response_from_exception,
|
||||
e=RuntimeError(json_response_from_exception['failure_message']),
|
||||
error_message=WebController._captured_status_codes[__exception_code],
|
||||
log_level='warn',
|
||||
response_code=__exception_code)
|
||||
|
||||
#
|
||||
# Unhandled Server Errors
|
||||
#
|
||||
except WorkerPoolException as e:
|
||||
# special case since WorkerPoolException contain stack traces - not ideal for returning from REST endpoints
|
||||
__exception_code = 500
|
||||
if self.crash_on_error:
|
||||
raise
|
||||
|
||||
if isinstance(e, WorkerPool.TimedOut):
|
||||
message_prefix = f"Execution timed out after {e.timeout}s"
|
||||
else:
|
||||
message_prefix = f"Execution failed - no more values to try"
|
||||
|
||||
# get random failure for context
|
||||
if e.failures:
|
||||
value = list(e.failures)[0]
|
||||
_, exception, _ = e.failures[value]
|
||||
msg = f"{message_prefix} ({len(e.failures)} concurrent failures recorded); " \
|
||||
f"for example, for {value}: {exception}"
|
||||
else:
|
||||
msg = message_prefix
|
||||
|
||||
return self.emitter.exception(
|
||||
e=RuntimeError(msg),
|
||||
log_level='warn',
|
||||
response_code=__exception_code,
|
||||
error_message=WebController._captured_status_codes[__exception_code])
|
||||
|
||||
except Exception as e:
|
||||
__exception_code = 500
|
||||
if self.crash_on_error:
|
||||
|
@ -392,4 +392,4 @@ class WebController(InterfaceControlServer):
|
|||
#
|
||||
else:
|
||||
self.log.debug(f"{method_name} [200 - OK]")
|
||||
return self.emitter.respond(response=response)
|
||||
return self.emitter.respond(json_response=response)
|
||||
|
|
|
@ -243,6 +243,14 @@ class WebEmitter:
|
|||
|
||||
self.log = Logger('web-emitter')
|
||||
|
||||
def _log_exception(self, e, error_message, log_level, response_code):
|
||||
exception = f"{type(e).__name__}: {str(e)}" if str(e) else type(e).__name__
|
||||
message = f"{self} [{str(response_code)} - {error_message}] | ERROR: {exception}"
|
||||
logger = getattr(self.log, log_level)
|
||||
# See #724 / 2156
|
||||
message_cleaned_for_logger = message.replace("{", "<^<").replace("}", ">^>")
|
||||
logger(message_cleaned_for_logger)
|
||||
|
||||
@staticmethod
|
||||
def assemble_response(response: dict) -> dict:
|
||||
response_data = {'result': response,
|
||||
|
@ -255,25 +263,35 @@ class WebEmitter:
|
|||
log_level: str = 'info',
|
||||
response_code: int = 500):
|
||||
|
||||
exception = f"{type(e).__name__}: {str(e)}" if str(e) else type(e).__name__
|
||||
message = f"{self} [{str(response_code)} - {error_message}] | ERROR: {exception}"
|
||||
logger = getattr(self.log, log_level)
|
||||
# See #724 / 2156
|
||||
message_cleaned_for_logger = message.replace("{", "<^<").replace("}", ">^>")
|
||||
logger(message_cleaned_for_logger)
|
||||
self._log_exception(e, error_message, log_level, response_code)
|
||||
if self.crash_on_error:
|
||||
raise e
|
||||
|
||||
response_message = str(e) or type(e).__name__
|
||||
return self.sink(response_message, status=response_code)
|
||||
|
||||
def respond(self, response) -> Response:
|
||||
assembled_response = self.assemble_response(response=response)
|
||||
def exception_with_response(self,
|
||||
json_error_response,
|
||||
e,
|
||||
error_message: str,
|
||||
log_level: str = 'info',
|
||||
response_code: int = 500):
|
||||
self._log_exception(e, error_message, log_level, response_code)
|
||||
if self.crash_on_error:
|
||||
raise e
|
||||
|
||||
assembled_response = self.assemble_response(response=json_error_response)
|
||||
serialized_response = WebEmitter.transport_serializer(assembled_response)
|
||||
|
||||
# ---------- HTTP OUTPUT
|
||||
response = self.sink(response=serialized_response, status=HTTPStatus.OK, content_type="application/json")
|
||||
return response
|
||||
json_response = self.sink(response=serialized_response, status=response_code, content_type="application/json")
|
||||
return json_response
|
||||
|
||||
def respond(self, json_response) -> Response:
|
||||
assembled_response = self.assemble_response(response=json_response)
|
||||
serialized_response = WebEmitter.transport_serializer(assembled_response)
|
||||
|
||||
json_response = self.sink(response=serialized_response, status=HTTPStatus.OK, content_type="application/json")
|
||||
return json_response
|
||||
|
||||
def get_stream(self, *args, **kwargs):
|
||||
return null_stream()
|
||||
|
|
Loading…
Reference in New Issue