Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions solnlib/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import logging.handlers
import os.path as op
import traceback
from functools import partial
from threading import Lock
from typing import Dict, Any

Expand Down Expand Up @@ -251,6 +252,31 @@ def modular_input_end(logger: logging.Logger, modular_input_name: str):
)


def _base_error_log(
logger,
exc: Exception,
exe_label,
full_msg: bool = True,
msg_before: str = None,
msg_after: str = None,
):
log_exception(
logger,
exc,
exc_label=exe_label,
full_msg=full_msg,
msg_before=msg_before,
msg_after=msg_after,
)


log_connection_error = partial(_base_error_log, exe_label="Connection Error")
log_configuration_error = partial(_base_error_log, exe_label="Configuration Error")
log_permission_error = partial(_base_error_log, exe_label="Permission Error")
log_authentication_error = partial(_base_error_log, exe_label="Authentication Error")
log_server_error = partial(_base_error_log, exe_label="Server Error")


def events_ingested(
logger: logging.Logger,
modular_input_name: str,
Expand Down Expand Up @@ -303,12 +329,34 @@ def events_ingested(
def log_exception(
logger: logging.Logger,
e: Exception,
exc_label: str,
full_msg: bool = True,
msg_before: str = None,
msg_after: str = None,
log_level: int = logging.ERROR,
):
"""General function to log exceptions."""
"""General function to log exceptions.

Arguments:
logger: Add-on logger.
e: Exception to log.
exc_label: label for the error to categorize it.
full_msg: if set to True, full traceback will be logged. Default: True
msg_before: custom message before exception traceback. Default: None
msg_after: custom message after exception traceback. Default: None
log_level: Log level to log exception. Default: ERROR.
"""

msg = _get_exception_message(e, full_msg, msg_before, msg_after)
logger.log(log_level, f'exc_l="{exc_label}" {msg}')


def _get_exception_message(
e: Exception,
full_msg: bool = True,
msg_before: str = None,
msg_after: str = None,
) -> str:
exc_type, exc_value, exc_traceback = type(e), e, e.__traceback__
if full_msg:
error = traceback.format_exception(exc_type, exc_value, exc_traceback)
Expand All @@ -318,5 +366,4 @@ def log_exception(
msg_start = msg_before if msg_before is not None else ""
msg_mid = "".join(error)
msg_end = msg_after if msg_after is not None else ""
msg = f"{msg_start}\n{msg_mid}\n{msg_end}"
logger.log(log_level, msg)
return f"{msg_start}\n{msg_mid}\n{msg_end}"
41 changes: 36 additions & 5 deletions tests/unit/test_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,10 @@ def test_log_exceptions_full_msg():
test_jsons = "{'a': 'aa'"
json.loads(test_jsons)
except Exception as e:
log.log_exception(mock_logger, e, msg_before=start_msg)
log.log_exception(mock_logger, e, "test type1", msg_before=start_msg)
mock_logger.log.assert_called_with(
logging.ERROR, f"{start_msg}\n{traceback.format_exc()}\n"
logging.ERROR,
f'exc_l="test type1" {start_msg}\n{traceback.format_exc()}\n',
)


Expand All @@ -262,10 +263,40 @@ def test_log_exceptions_partial_msg():
json.loads(test_jsons)
except Exception as e:
log.log_exception(
mock_logger, e, full_msg=False, msg_before=start_msg, msg_after=end_msg
mock_logger,
e,
exc_label="test type",
full_msg=False,
msg_before=start_msg,
msg_after=end_msg,
)
mock_logger.log.assert_called_with(
logging.ERROR,
"some msg before exception\njson.decoder.JSONDecodeError: Expecting property name enclosed in double "
"quotes: line 1 column 2 (char 1)\n\nsome msg after exception",
'exc_l="test type" some msg before exception\njson.decoder.JSONDecodeError: Expecting property '
"name enclosed in double quotes: line 1 column 2 (char 1)\n\nsome msg after exception",
)


@pytest.mark.parametrize(
"func,result",
[
("log_connection_error", '"Connection Error"'),
("log_configuration_error", '"Configuration Error"'),
("log_permission_error", '"Permission Error"'),
("log_authentication_error", '"Authentication Error"'),
("log_server_error", '"Server Error"'),
],
)
def test_log_basic_error(func, result):
class AddonComplexError(Exception):
pass

with mock.patch("logging.Logger") as mock_logger:
try:
raise AddonComplexError
except AddonComplexError as e:
fun = getattr(log, func)
fun(mock_logger, e)
mock_logger.log.assert_called_with(
logging.ERROR, f"exc_l={result} \n{traceback.format_exc()}\n"
)