def get_error(self, worker_ctx, exc_info): """ Transform exception to serialisable dictionary """ exc_type, exc, traceback = exc_info expected_exceptions = getattr(worker_ctx.entrypoint, 'expected_exceptions', None) expected_exceptions = expected_exceptions or tuple() is_expected = isinstance(exc, expected_exceptions) try: exc_traceback = ''.join(format_exception(*exc_info)) except Exception: exc_traceback = 'traceback serialisation failed' return { 'exc_type': exc_type.__name__, 'exc_path': get_module_path(exc_type), 'exc_args': utils.safe_for_serialisation(exc.args), 'exc_value': utils.safe_for_serialisation(exc), 'traceback': exc_traceback, 'is_expected': is_expected, }
def test_safe_for_serialisation_repr_failure(): class CannotRepr(object): def __repr__(self): raise Exception('boom') assert (utils.safe_for_serialisation( [1, CannotRepr(), 2]) == [1, 'failed to get string representation', 2])
def process(self, message, kwargs): """ Extract useful entrypoint processing information Extract useful entrypoint information and set it as ``constants.TRACE_KEY`` named attribute of the log record. Content of this attribute should be easily serialisable and the aim is to fill it with something that can go easily over a wire and that can be easily stored, filtered and searched. """ hostname = self.extra['hostname'] stage = kwargs['extra']['stage'] worker_ctx = kwargs['extra']['worker_ctx'] timestamp = kwargs['extra']['timestamp'] entrypoint = worker_ctx.entrypoint data = kwargs['extra'].get(constants.TRACE_KEY, {}) data[constants.TIMESTAMP_KEY] = timestamp data[constants.HOSTNAME_KEY] = hostname data[constants.SERVICE_NAME_KEY] = worker_ctx.service_name data[constants.ENTRYPOINT_TYPE_KEY] = type(entrypoint).__name__ data[constants.ENTRYPOINT_NAME_KEY] = entrypoint.method_name data[constants.CONTEXT_DATA_KEY] = utils.safe_for_serialisation( worker_ctx.data) data[constants.CALL_ID_KEY] = worker_ctx.call_id data[constants.CALL_ID_STACK_KEY] = worker_ctx.call_id_stack data[constants.ORIGIN_CALL_ID_KEY] = worker_ctx.origin_call_id data[constants.STAGE_KEY] = stage.value call_args, call_args_redacted = self.get_call_args(worker_ctx) data[constants.REQUEST_KEY] = call_args data[constants.REQUEST_REDACTED_KEY] = call_args_redacted if stage == constants.Stage.response: exc_info = kwargs['extra']['exc_info_'] if exc_info: data[constants.RESPONSE_STATUS_KEY] = ( constants.Status.error.value) self.set_exception(data, worker_ctx, exc_info) else: data[constants.RESPONSE_STATUS_KEY] = ( constants.Status.success.value) result = kwargs['extra']['result'] data[constants.RESPONSE_KEY] = self.get_result(result) data[constants.RESPONSE_TIME_KEY] = ( kwargs['extra']['response_time']) kwargs['extra'][constants.TRACE_KEY] = data return message, kwargs
def set_exception(self, data, worker_ctx, exc_info): """ Set exception details as serialisable trace attributes """ exc_type, exc, _ = exc_info expected_exceptions = getattr(worker_ctx.entrypoint, 'expected_exceptions', None) expected_exceptions = expected_exceptions or tuple() is_expected = isinstance(exc, expected_exceptions) try: exc_traceback = ''.join(format_exception(*exc_info)) except Exception: exc_traceback = 'traceback serialisation failed' exc_args = utils.safe_for_serialisation(exc.args) data[constants.EXCEPTION_TYPE_KEY] = exc_type.__name__ data[constants.EXCEPTION_PATH_KEY] = get_module_path(exc_type) data[constants.EXCEPTION_ARGS_KEY] = exc_args data[constants.EXCEPTION_VALUE_KEY] = utils.safe_for_serialisation(exc) data[constants.EXCEPTION_TRACEBACK_KEY] = exc_traceback data[constants.EXCEPTION_EXPECTED_KEY] = is_expected
def get_call_args(self, worker_ctx): """ Transform request object to serialized dictionary """ entrypoint = worker_ctx.entrypoint method = getattr(entrypoint.container.service_cls, entrypoint.method_name) call_args = inspect.getcallargs(method, None, *worker_ctx.args, **worker_ctx.kwargs) del call_args['self'] request = call_args.pop('request') data = request.data or request.form call_args['request'] = { 'url': request.url, 'method': request.method, 'data': utils.safe_for_serialisation(data), 'headers': dict(self.get_headers(request.environ)), 'env': dict(self.get_environ(request.environ)), } return call_args, False
def get_result(self, result): """ Return serialisable result data """ return utils.safe_for_serialisation(result)
def test_safe_for_serialisation(input_, expected_output): assert utils.safe_for_serialisation(input_) == expected_output