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
|
- ``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).
|
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.
|
- ``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
|
- ``500 INTERNAL SERVER ERROR`` -- The server encountered an unexpected condition that prevented it from
|
||||||
fulfilling the request.
|
fulfilling the request.
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ from hendrix.deploy.base import HendrixDeploy
|
||||||
from hendrix.deploy.tls import HendrixDeployTLS
|
from hendrix.deploy.tls import HendrixDeployTLS
|
||||||
from twisted.internet import reactor, stdio
|
from twisted.internet import reactor, stdio
|
||||||
|
|
||||||
|
from nucypher.cli.processes import JSONRPCLineReceiver
|
||||||
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
|
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
|
||||||
from nucypher.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter, WebEmitter
|
from nucypher.control.emitters import StdoutEmitter, JSONRPCStdoutEmitter, WebEmitter
|
||||||
from nucypher.control.interfaces import ControlInterface
|
from nucypher.control.interfaces import ControlInterface
|
||||||
|
@ -250,6 +251,7 @@ class WebController(InterfaceControlServer):
|
||||||
|
|
||||||
_captured_status_codes = {200: 'OK',
|
_captured_status_codes = {200: 'OK',
|
||||||
400: 'BAD REQUEST',
|
400: 'BAD REQUEST',
|
||||||
|
404: 'NOT FOUND',
|
||||||
500: 'INTERNAL SERVER ERROR'}
|
500: 'INTERNAL SERVER ERROR'}
|
||||||
|
|
||||||
def test_client(self):
|
def test_client(self):
|
||||||
|
@ -302,8 +304,27 @@ class WebController(InterfaceControlServer):
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return self.handle_request(*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,
|
_400_exceptions = (SpecificationError,
|
||||||
TypeError,
|
TypeError,
|
||||||
JSONDecodeError,
|
JSONDecodeError,
|
||||||
|
@ -336,47 +357,26 @@ class WebController(InterfaceControlServer):
|
||||||
error_message=WebController._captured_status_codes[__exception_code])
|
error_message=WebController._captured_status_codes[__exception_code])
|
||||||
|
|
||||||
#
|
#
|
||||||
# Server Errors
|
# Execution Errors
|
||||||
#
|
#
|
||||||
except SpecificationError as e:
|
except WorkerPoolException as e:
|
||||||
__exception_code = 500
|
# special case since WorkerPoolException contains multiple stack traces
|
||||||
|
# - not ideal for returning from REST endpoints
|
||||||
|
__exception_code = 404
|
||||||
if self.crash_on_error:
|
if self.crash_on_error:
|
||||||
raise
|
raise
|
||||||
return self.emitter.exception(
|
|
||||||
e=e,
|
json_response_from_exception = self.json_response_from_worker_pool_exception(e)
|
||||||
log_level='critical',
|
return self.emitter.exception_with_response(
|
||||||
response_code=__exception_code,
|
json_error_response=json_response_from_exception,
|
||||||
error_message=WebController._captured_status_codes[__exception_code])
|
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
|
# 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:
|
except Exception as e:
|
||||||
__exception_code = 500
|
__exception_code = 500
|
||||||
if self.crash_on_error:
|
if self.crash_on_error:
|
||||||
|
@ -392,4 +392,4 @@ class WebController(InterfaceControlServer):
|
||||||
#
|
#
|
||||||
else:
|
else:
|
||||||
self.log.debug(f"{method_name} [200 - OK]")
|
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')
|
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
|
@staticmethod
|
||||||
def assemble_response(response: dict) -> dict:
|
def assemble_response(response: dict) -> dict:
|
||||||
response_data = {'result': response,
|
response_data = {'result': response,
|
||||||
|
@ -255,25 +263,35 @@ class WebEmitter:
|
||||||
log_level: str = 'info',
|
log_level: str = 'info',
|
||||||
response_code: int = 500):
|
response_code: int = 500):
|
||||||
|
|
||||||
exception = f"{type(e).__name__}: {str(e)}" if str(e) else type(e).__name__
|
self._log_exception(e, error_message, log_level, response_code)
|
||||||
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)
|
|
||||||
if self.crash_on_error:
|
if self.crash_on_error:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
response_message = str(e) or type(e).__name__
|
response_message = str(e) or type(e).__name__
|
||||||
return self.sink(response_message, status=response_code)
|
return self.sink(response_message, status=response_code)
|
||||||
|
|
||||||
def respond(self, response) -> Response:
|
def exception_with_response(self,
|
||||||
assembled_response = self.assemble_response(response=response)
|
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)
|
serialized_response = WebEmitter.transport_serializer(assembled_response)
|
||||||
|
|
||||||
# ---------- HTTP OUTPUT
|
json_response = self.sink(response=serialized_response, status=response_code, content_type="application/json")
|
||||||
response = self.sink(response=serialized_response, status=HTTPStatus.OK, content_type="application/json")
|
return json_response
|
||||||
return 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):
|
def get_stream(self, *args, **kwargs):
|
||||||
return null_stream()
|
return null_stream()
|
||||||
|
|
Loading…
Reference in New Issue