diff --git a/solnlib/log.py b/solnlib/log.py index 9bd04c7f..7037b120 100644 --- a/solnlib/log.py +++ b/solnlib/log.py @@ -19,6 +19,7 @@ import logging import logging.handlers import os.path as op +import traceback from threading import Lock from typing import Dict, Any @@ -263,3 +264,25 @@ def events_ingested( "n_events": n_events, }, ) + + +def log_exception( + logger: logging.Logger, + e: Exception, + full_msg: bool = True, + msg_before: str = None, + msg_after: str = None, + log_level: int = logging.ERROR, +): + """General function to log exceptions.""" + exc_type, exc_value, exc_traceback = type(e), e, e.__traceback__ + if full_msg: + error = traceback.format_exception(exc_type, exc_value, exc_traceback) + else: + error = traceback.format_exception_only(exc_type, exc_value) + + 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) diff --git a/tests/unit/test_log.py b/tests/unit/test_log.py index 93fa5de2..d19a14f0 100644 --- a/tests/unit/test_log.py +++ b/tests/unit/test_log.py @@ -15,10 +15,12 @@ # import logging +import json import multiprocessing import os import shutil import threading +import traceback import time from unittest import mock @@ -197,3 +199,34 @@ def test_events_ingested(): logging.INFO, "action=events_ingested modular_input_name=modular_input_name sourcetype_ingested=sourcetype n_events=5", ) + + +def test_log_exceptions_full_msg(): + start_msg = "some msg before exception" + with mock.patch("logging.Logger") as mock_logger: + try: + test_jsons = "{'a': 'aa'" + json.loads(test_jsons) + except Exception as e: + log.log_exception(mock_logger, e, msg_before=start_msg) + mock_logger.log.assert_called_with( + logging.ERROR, f"{start_msg}\n{traceback.format_exc()}\n" + ) + + +def test_log_exceptions_partial_msg(): + start_msg = "some msg before exception" + end_msg = "some msg after exception" + with mock.patch("logging.Logger") as mock_logger: + try: + test_jsons = "{'a': 'aa'" + 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.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", + )