From 27e25e757ae9662033e79d3ac50a12de9052ec71 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Mon, 23 Mar 2020 01:10:53 -0700 Subject: [PATCH] improve unknown error parsing --- hyperglass/execution/execute.py | 21 +++++-------- hyperglass/util.py | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/hyperglass/execution/execute.py b/hyperglass/execution/execute.py index 1366890..474ff25 100644 --- a/hyperglass/execution/execute.py +++ b/hyperglass/execution/execute.py @@ -7,7 +7,6 @@ hyperglass-frr API calls, returns the output back to the front end. """ # Standard Library -import re import signal from ssl import CertificateError @@ -23,7 +22,7 @@ from netmiko import ( ) # Project -from hyperglass.util import log +from hyperglass.util import log, parse_exception from hyperglass.encode import jwt_decode, jwt_encode from hyperglass.constants import Supported from hyperglass.exceptions import ( @@ -317,14 +316,12 @@ class Connect: response = "\n\n".join(responses) log.debug(f"Output for query {self.query}:\n{response}") except httpx.exceptions.HTTPError as rest_error: - rest_msg = " ".join( - re.findall(r"[A-Z][^A-Z]*", rest_error.__class__.__name__) - ) - log.error(f"Error connecting to device {self.device.name}: {rest_msg}") + msg = parse_exception(rest_error) + log.error(f"Error connecting to device {self.device.name}: {msg}") raise RestError( params.messages.connection_error, device_name=self.device.display_name, - error=rest_msg, + error=msg, ) except OSError as ose: log.critical(str(ose)) @@ -335,13 +332,11 @@ class Connect: ) except CertificateError as cert_error: log.critical(str(cert_error)) - msg_summary = " ".join( - re.findall(r"[A-Z][^A-Z]*", cert_error.__class__.__name__) - ) + msg = parse_exception(cert_error) raise RestError( params.messages.connection_error, device_name=self.device.display_name, - error=f"{msg_summary}: {cert_error}", + error=f"{msg}: {cert_error}", ) if raw_response.status_code != 200: @@ -404,11 +399,11 @@ class Execute: else: output = await connect.scrape_direct() - if output == "": + if output == "" or output == "\n": raise ResponseEmpty( params.messages.no_output, device_name=device.display_name ) - log.debug(f"Output for query: {self.query_data}:\n{output}") + log.debug(f"Output for query: {self.query_data.json()}:\n{repr(output)}") return output diff --git a/hyperglass/util.py b/hyperglass/util.py index faa94f7..d3d395f 100644 --- a/hyperglass/util.py +++ b/hyperglass/util.py @@ -616,3 +616,55 @@ def import_public_key(app_path, device_name, keystring): raise RuntimeError("Wrote key, but written file did not match input key") return True + + +def split_on_uppercase(s): + """Split characters by uppercase letters. + + From: https://stackoverflow.com/a/40382663 + + """ + string_length = len(s) + is_lower_around = ( + lambda: s[i - 1].islower() or string_length > (i + 1) and s[i + 1].islower() + ) + + start = 0 + parts = [] + for i in range(1, string_length): + if s[i].isupper() and is_lower_around(): + parts.append(s[start:i]) + start = i + parts.append(s[start:]) + + return parts + + +def parse_exception(exc): + """Parse an exception and its direct cause.""" + + if not isinstance(exc, BaseException): + raise TypeError(f"'{repr(exc)}' is not an exception.") + + def get_exc_name(exc): + return " ".join(split_on_uppercase(exc.__class__.__name__)) + + def get_doc_summary(doc): + return doc.strip().split("\n")[0].strip(".") + + name = get_exc_name(exc) + parsed = [] + if exc.__doc__: + detail = get_doc_summary(exc.__doc__) + parsed.append(f"{name} ({detail})") + else: + parsed.append(name) + + if exc.__cause__: + cause = get_exc_name(exc.__cause__) + if exc.__cause__.__doc__: + cause_detail = get_doc_summary(exc.__cause__.__doc__) + parsed.append(f"{cause} ({cause_detail})") + else: + parsed.append(cause) + return ", caused by ".join(parsed)