Skip to content
Snippets Groups Projects
Commit 683d586a authored by Jakub Maloštík's avatar Jakub Maloštík
Browse files

Use ErrorMessage class for errors instead of dicts

parent e18f658d
No related branches found
No related tags found
No related merge requests found
...@@ -51,94 +51,134 @@ class Encoder(json.JSONEncoder): ...@@ -51,94 +51,134 @@ class Encoder(json.JSONEncoder):
def default(self, o): def default(self, o):
if isinstance(o, Error): if isinstance(o, Error):
return o.to_dict() return o.to_dict()
if isinstance(o, ErrorMessage):
out = o.other_args.copy()
out.pop("exc", None)
out["error"] = o.error
out["message"] = o.message
if o.events:
out["events"] = list(o.events)
return out
return str(o) return str(o)
class ErrorMessage(Exception):
def __init__(self, error, message, events=None, unique_id=None, **kwargs):
super(Exception, self).__setattr__("error", error)
super(Exception, self).__setattr__("message", message)
super(Exception, self).__setattr__("unique_id", unique_id)
self.events = set() if events is None else set(events)
self.other_args = kwargs
def __repr__(self):
return "%s(error=%d, message=%s)" % (
type(self).__name__, self.error, repr(self.message)
)
def __str__(self):
if sys.version_info[0] < 3:
return self.str_err().encode('ascii', 'backslashereplace')
return self.str_err()
def str_err(self):
exc = self.other_args.get("exc", None)
if exc in (None, (None, None, None)):
exc_cause = ""
else:
exc_cause = " (cause was %s: %s)" % (exc[0].__name__, str(exc[1]))
return "Error(%s) %s%s" % (self.error, self.message, exc_cause)
def str_info(self):
arg_copy = self.other_args.copy()
arg_copy.pop("req_id", None)
arg_copy.pop("method", None)
arg_copy.pop("exc", None)
if arg_copy:
return "Detail: %s" % json.dumps(arg_copy, cls=Encoder)
return ""
def str_debug(self):
exc = self.other_args.get("exc", None)
if exc in (None, (None, None, None)):
return ""
exc_tb = exc[2]
if not exc_tb:
return ""
return "Traceback:\n" + "".join(format_tb(exc_tb))
def __getattr__(self, name):
if name in self.other_args:
return self.other_args[name]
raise AttributeError
def __setattr__(self, name, value):
if name in ("events", "exc", "other_args"):
super(Exception, self).__setattr__(name, value)
return
if name in ("error", "message", "unique_id"):
raise AttributeError("Cannot change the attribute %s" % name)
self.other_args[name] = value
class Error(Exception): class Error(Exception):
def __init__(self, method=None, req_id=None, errors=None, **kwargs): def __init__(self, method=None, req_id=None, errors=None, **kwargs):
self.method = method self.method = method
self.req_id = req_id self.req_id = req_id
self.errors = [kwargs] if kwargs else [] if "message" in kwargs:
kwargs.setdefault("error", 500)
self.errors = [ErrorMessage(**kwargs)]
else:
self.errors = []
if errors: if errors:
self.errors.extend(errors) self.errors.extend(errors)
def append(self, _events=None, **kwargs): def append(self, _events=None, **kwargs):
self.errors.append(kwargs) kwargs.setdefault("message", "No further information")
kwargs.setdefault("error", 500)
self.errors.append(ErrorMessage(**kwargs))
def get_http_err_msg(self): def get_http_err_msg(self):
try: try:
err = self.errors[0]["error"] err = self.errors[0].error
msg = self.errors[0]["message"].replace("\n", " ") msg = self.errors[0].message
except (IndexError, KeyError): except (IndexError, AttributeError):
err = 500 err = 500
msg = "There's NO self-destruction button! Ah, you've just found it..." msg = "There's NO self-destruction button! Ah, you've just found it..."
for e in self.errors: return err, msg
next_err = e.get("error", 500)
if err != next_err: if not all(msg == e.message for e in self.errors):
# messages not the same, get Multiple errors
msg = "Multiple errors"
if not all(err == e.error for e in self.errors):
# errors not same, round to basic err code (400, 500) # errors not same, round to basic err code (400, 500)
# and use the highest one # and use the highest one
err = max(err//100, next_err//100)*100 err = max(e.error for e in self.errors) // 100 * 100
next_msg = e.get("message", "Unknown error").replace("\n", " ")
if msg != next_msg:
msg = "Multiple errors"
msg = "".join((c if '\x20' <= c != '\x7f' else r'\x{:02x}'.format(ord(c))) for c in msg) # escape control characters msg = "".join((c if '\x20' <= c != '\x7f' else r'\x{:02x}'.format(ord(c))) for c in msg) # escape control characters
return err, msg return err, msg
def __str__(self): def __str__(self):
return "\n".join(self.str_err(e) for e in self.errors) return "\n".join(str(e) for e in self.errors)
def log(self, logger, prio=logging.ERROR): def log(self, logger, prio=logging.ERROR):
for e in self.errors: for e in self.errors:
logger.log(prio, self.str_err(e)) logger.log(prio, e.str_err())
info = self.str_info(e) info = e.str_info()
if info: if info:
logger.info(info) logger.info(info)
debug = self.str_debug(e) debug = e.str_debug()
if debug: if debug:
logger.debug(debug) logger.debug(debug)
def str_err(self, e):
out = []
out.append("Error(%s) %s " % (e.get("error", 0), e.get("message", "Unknown error")))
if "exc" in e and e["exc"]:
out.append("(cause was %s: %s)" % (e["exc"][0].__name__, str(e["exc"][1])))
return "".join(out)
def str_info(self, e):
ecopy = dict(e) # shallow copy
ecopy.pop("req_id", None)
ecopy.pop("method", None)
ecopy.pop("error", None)
ecopy.pop("message", None)
ecopy.pop("exc", None)
if ecopy:
out = "Detail: %s" % (json.dumps(ecopy, cls=Encoder))
else:
out = ""
return out
def str_debug(self, e):
out = []
if not e.get("exc"):
return ""
exc_tb = e["exc"][2]
if exc_tb:
out.append("Traceback:\n")
out.extend(format_tb(exc_tb))
return "".join(out)
def to_dict(self): def to_dict(self):
errlist = []
for e in self.errors:
ecopy = dict(e)
ecopy.pop("exc", None)
errlist.append(ecopy)
d = { d = {
"method": self.method, "method": self.method,
"req_id": self.req_id, "req_id": self.req_id,
"errors": errlist "errors": self.errors
} }
return d return d
...@@ -466,14 +506,15 @@ class JSONSchemaValidator(NoValidator): ...@@ -466,14 +506,15 @@ class JSONSchemaValidator(NoValidator):
res = [] res = []
for error in sorted(self.validator.iter_errors(event), key=sortkey): for error in sorted(self.validator.iter_errors(event), key=sortkey):
res.append({ res.append(
"error": 460, ErrorMessage(
"message": "Validation error: key \"%s\", value \"%s\"" % ( 460, "Validation error: key \"%s\", value \"%s\"" % (
"/".join(str(v) for v in error.path), "/".join(map(str, error.path)),
error.instance error.instance
), ),
"expected": error.schema.get('description', 'no additional info') expected=error.schema.get('description', 'no additional info')
}) )
)
return res return res
...@@ -806,7 +847,7 @@ class DataBase(ObjectBase): ...@@ -806,7 +847,7 @@ class DataBase(ObjectBase):
except Exception as e: except Exception as e:
exception = self.req.error(message="DB error", error=500, exc=sys.exc_info(), env=self.req.env) exception = self.req.error(message="DB error", error=500, exc=sys.exc_info(), env=self.req.env)
exception.log(self.log) exception.log(self.log)
return [{"error": 500, "message": "DB error %s" % type(e).__name__}] return [ErrorMessage(500, "DB error %s" % type(e).__name__)]
@abc.abstractmethod @abc.abstractmethod
def _build_insert_last_received_id(self, client, id): def _build_insert_last_received_id(self, client, id):
...@@ -1688,28 +1729,23 @@ class WardenHandler(ObjectBase): ...@@ -1688,28 +1729,23 @@ class WardenHandler(ObjectBase):
return res return res
def check_node(self, event, name): def check_node(self, event, event_indx, name):
try: try:
ev_id = event['Node'][0]['Name'].lower() ev_id = event['Node'][0]['Name'].lower()
except (KeyError, TypeError, IndexError): except (KeyError, TypeError, IndexError):
# Event does not bear valid Node attribute # Event does not bear valid Node attribute
return [{"error": 422, "message": "Event does not bear valid Node attribute"}] return [
ErrorMessage(422, "Event does not bear valid Node attribute", {event_indx})
]
if ev_id != name: if ev_id != name:
return [{"error": 422, "message": "Node does not correspond with saving client"}] return [
ErrorMessage(422, "Node does not correspond with saving client", {event_indx})
]
return [] return []
def add_event_nums(self, ilist, events, errlist): def add_errors(self, errs_to_add):
for err in errlist: for err in errs_to_add:
err.setdefault("events", []).extend(ilist) self.errs.setdefault((err.error, err.message, err.unique_id), err).events.update(err.events)
ev_ids = err.setdefault("events_id", [])
for i in ilist:
event = events[i]
try:
id = event["ID"]
except (KeyError, TypeError, ValueError):
id = None
ev_ids.append(id)
return errlist
@expose(write=True) @expose(write=True)
@json_wrapper @json_wrapper
...@@ -1717,40 +1753,55 @@ class WardenHandler(ObjectBase): ...@@ -1717,40 +1753,55 @@ class WardenHandler(ObjectBase):
if not isinstance(events, list): if not isinstance(events, list):
raise self.req.error(message="List of events expected.", error=400) raise self.req.error(message="List of events expected.", error=400)
errs = [] self.errs = {}
if len(events) > self.send_events_limit: if len(events) > self.send_events_limit:
errs.extend(self.add_event_nums(range(self.send_events_limit, len(events)), events, [ self.add_errors(
{"error": 507, "message": "Too much events in one batch.", "send_events_limit": self.send_events_limit}])) [
ErrorMessage(
507, "Too many events in one batch.",
set(range(self.send_events_limit, len(events))),
send_events_limit=self.send_events_limit
)
]
)
saved = 0
events_tosend = [] events_tosend = []
events_raw = [] events_raw = []
events_nums = [] events_nums = []
for i, event in enumerate(events[0:self.send_events_limit]): for i, event in enumerate(events[0:self.send_events_limit]):
v_errs = self.validator.check(event) v_errs = self.validator.check(event)
if v_errs: if v_errs:
errs.extend(self.add_event_nums([i], events, v_errs)) self.add_errors(v_errs)
continue continue
node_errs = self.check_node(event, self.req.client.name) node_errs = self.check_node(event, i, self.req.client.name)
if node_errs: if node_errs:
errs.extend(self.add_event_nums([i], events, node_errs)) self.add_errors(node_errs)
continue continue
if self.req.client.test and 'Test' not in event.get('Category', []): if self.req.client.test and 'Test' not in event.get('Category', []):
errs.extend( self.add_errors(
self.add_event_nums([i], events, [{ [
"error": 422, ErrorMessage(
"message": "You're allowed to send only messages, containing \"Test\" among categories.", 422, "You're allowed to send only messages containing \"Test\" among categories.", {i},
"categories": event.get('Category', [])}])) # Ensure that 1the error message is contained for every combination of categories
unique_id=tuple(event.get('Category', [])),
categories=event.get('Category', [])
)
]
)
continue continue
raw_event = json.dumps(event) raw_event = json.dumps(event)
if len(raw_event) >= self.db.event_size_limit: if len(raw_event) >= self.db.event_size_limit:
errs.extend( self.add_errors(
self.add_event_nums([i], events, [ [
{"error": 413, "message": "Event too long (>%i B)" % self.db.event_size_limit} ErrorMessage(
])) 413, "Event too long (>%i B)" % self.db.event_size_limit, {i},
event_size_limit = self.db.event_size_limit
)
]
)
continue continue
events_tosend.append(event) events_tosend.append(event)
...@@ -1758,15 +1809,13 @@ class WardenHandler(ObjectBase): ...@@ -1758,15 +1809,13 @@ class WardenHandler(ObjectBase):
events_nums.append(i) events_nums.append(i)
db_errs = self.db.store_events(self.req.client, events_tosend, events_raw) db_errs = self.db.store_events(self.req.client, events_tosend, events_raw)
if db_errs: self.add_errors(db_errs)
errs.extend(self.add_event_nums(events_nums, events_tosend, db_errs))
saved = 0 saved = 0 if db_errs else len(events_tosend)
else:
saved = len(events_tosend)
self.log.info("Saved %i events" % saved) self.log.info("Saved %i events" % saved)
if errs: if self.errs:
raise self.req.error(errors=errs) raise self.req.error(errors=self.errs.values())
return {"saved": saved} return {"saved": saved}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment