hitl_tester.modules.logger

This module provides logging capabilities to both a file (defined in the yaml config) and the console. It may be used by any other module.

(c) 2020-2024 TurnAround Factor, Inc.

CUI DISTRIBUTION CONTROL Controlled by: DLA J68 R&D SBIP CUI Category: Small Business Research and Technology Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS POC: GOV SBIP Program Manager Denise Price, 571-767-0111 Distribution authorized to U.S. Government Agencies only, to protect information not owned by the U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, Fort Belvoir, VA 22060-6221

SBIR DATA RIGHTS Contract No.:SP4701-23-C-0083 Contractor Name: TurnAround Factor, Inc. Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 Expiration of SBIR Data Rights Period: September 24, 2029 The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause contained in the above identified contract. No restrictions apply after the expiration date shown above. Any reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce the markings.

  1"""
  2This module provides logging capabilities to both a file (defined in the yaml config) and the console.
  3It may be used by any other module.
  4
  5(c) 2020-2024 TurnAround Factor, Inc.
  6
  7CUI DISTRIBUTION CONTROL
  8Controlled by: DLA J68 R&D SBIP
  9CUI Category: Small Business Research and Technology
 10Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
 11POC: GOV SBIP Program Manager Denise Price, 571-767-0111
 12Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
 13U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
 14it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
 15for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
 16Fort Belvoir, VA 22060-6221
 17
 18SBIR DATA RIGHTS
 19Contract No.:SP4701-23-C-0083
 20Contractor Name: TurnAround Factor, Inc.
 21Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
 22Expiration of SBIR Data Rights Period: September 24, 2029
 23The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
 24software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
 25in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
 26contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
 27reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
 28the markings.
 29"""
 30
 31from __future__ import annotations
 32
 33import logging
 34import re
 35from pathlib import Path
 36from typing import ClassVar
 37
 38from typing_extensions import Self
 39
 40
 41class Logger:
 42    """Provides logging features for the HITL."""
 43
 44    instance: ClassVar[Self | None] = None
 45
 46    def __new__(cls):
 47        """Make Logger a singleton."""
 48        if cls.instance is None:
 49            cls.instance = super().__new__(cls)
 50        return cls.instance
 51
 52    def __init__(self, log_level: int = logging.DEBUG):
 53        """Create the logger object."""
 54        self.logger = logging.getLogger("HITL")
 55        self.logger.setLevel(log_level)
 56        self._html_result_text: list[str] = []
 57        self.log_file: Path = Path()
 58
 59        # Silence other modules
 60        for logger_name in ("pyvisa", "asyncio", "can"):
 61            logging.getLogger(logger_name).setLevel(logging.WARNING)
 62
 63    def use_file_mode(self, report_filename: Path, log_level: int = logging.DEBUG):
 64        """Switch to a file based log"""
 65        self.logger.setLevel(log_level)
 66
 67        class NoANSIFormatter(logging.Formatter):
 68            """Strips ANSI escape codes from the log message."""
 69
 70            def format(self, record):
 71                """Return logger message with terminal escapes removed."""
 72                record.msg = re.sub(r"\x1b\[[0-9;]*m", "", record.msg)
 73                return super().format(record)
 74
 75        formatter = NoANSIFormatter(
 76            "%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%m/%d/%Y %I:%M:%S%p"
 77        )
 78
 79        # Setup the date part of the filename
 80        self.log_file = Path(f"{report_filename}.txt")
 81
 82        file_log_handler = logging.FileHandler(self.log_file)
 83        file_log_handler.setLevel(self.logger.level)
 84        file_log_handler.setFormatter(formatter)
 85        self.logger.addHandler(file_log_handler)
 86        self.write_debug_to_report(f"Log Filename: {self.log_file}")
 87
 88    # TODO(JA): is it better to access self.logger.info (and friends) directly?
 89
 90    def write_result_to_report(self, result, exc_info=None):
 91        """Provide an interface to allow test cases to write test results to the test report."""
 92        self.logger.info(f"Test Result - {result}", exc_info=exc_info)
 93
 94    def write_debug_to_report(self, entry, exc_info=None):
 95        """Detailed information, useful only when a problem is being diagnosed."""
 96        self.logger.debug(str(entry), exc_info=exc_info)
 97
 98    def write_info_to_report(self, entry, exc_info=None):
 99        """This is used to confirm that everything is working as it should."""
100        self.logger.info(str(entry), exc_info=exc_info)
101
102    def write_warning_to_report(self, entry, exc_info=None):
103        """Something unexpected has happened or some problem is about to happen in the near future."""
104        self.logger.warning(str(entry), exc_info=exc_info)
105
106    def write_error_to_report(self, entry, exc_info=None):
107        """An error has occurred. The software was unable to perform some function."""
108        self.logger.error(str(entry), exc_info=exc_info)
109
110    def write_critical_to_report(self, entry, exc_info=None):
111        """A serious error has occurred. We may shut down or not be able to continue running properly."""
112        self.logger.critical(str(entry), exc_info=exc_info)
113
114    def write_result_to_html_report(self, text: str):
115        """Add text to the result section of the current test."""
116        self.logger.info(text)
117        self._html_result_text.append(text)
118
119    def write_warning_to_html_report(self, text: str):
120        """Add warning text to the result section of the current test."""
121        self.logger.warning(text)
122        self._html_result_text.append(f'<font color="#ca6500">⚠️ {text} </font>')
123
124    def write_failure_to_html_report(self, text: str):
125        """Add failing error text to the result section of the current test."""
126        self.logger.warning(text)
127        self._html_result_text.append(f'<font color="#990000">{text}</font>')
128
129    @property
130    def html_result_text(self) -> list[str]:
131        """Fetch and clear html text."""
132        result = self._html_result_text[:]
133        del self._html_result_text[:]
134        return result
135
136
137logger = Logger()
class Logger:
 42class Logger:
 43    """Provides logging features for the HITL."""
 44
 45    instance: ClassVar[Self | None] = None
 46
 47    def __new__(cls):
 48        """Make Logger a singleton."""
 49        if cls.instance is None:
 50            cls.instance = super().__new__(cls)
 51        return cls.instance
 52
 53    def __init__(self, log_level: int = logging.DEBUG):
 54        """Create the logger object."""
 55        self.logger = logging.getLogger("HITL")
 56        self.logger.setLevel(log_level)
 57        self._html_result_text: list[str] = []
 58        self.log_file: Path = Path()
 59
 60        # Silence other modules
 61        for logger_name in ("pyvisa", "asyncio", "can"):
 62            logging.getLogger(logger_name).setLevel(logging.WARNING)
 63
 64    def use_file_mode(self, report_filename: Path, log_level: int = logging.DEBUG):
 65        """Switch to a file based log"""
 66        self.logger.setLevel(log_level)
 67
 68        class NoANSIFormatter(logging.Formatter):
 69            """Strips ANSI escape codes from the log message."""
 70
 71            def format(self, record):
 72                """Return logger message with terminal escapes removed."""
 73                record.msg = re.sub(r"\x1b\[[0-9;]*m", "", record.msg)
 74                return super().format(record)
 75
 76        formatter = NoANSIFormatter(
 77            "%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%m/%d/%Y %I:%M:%S%p"
 78        )
 79
 80        # Setup the date part of the filename
 81        self.log_file = Path(f"{report_filename}.txt")
 82
 83        file_log_handler = logging.FileHandler(self.log_file)
 84        file_log_handler.setLevel(self.logger.level)
 85        file_log_handler.setFormatter(formatter)
 86        self.logger.addHandler(file_log_handler)
 87        self.write_debug_to_report(f"Log Filename: {self.log_file}")
 88
 89    # TODO(JA): is it better to access self.logger.info (and friends) directly?
 90
 91    def write_result_to_report(self, result, exc_info=None):
 92        """Provide an interface to allow test cases to write test results to the test report."""
 93        self.logger.info(f"Test Result - {result}", exc_info=exc_info)
 94
 95    def write_debug_to_report(self, entry, exc_info=None):
 96        """Detailed information, useful only when a problem is being diagnosed."""
 97        self.logger.debug(str(entry), exc_info=exc_info)
 98
 99    def write_info_to_report(self, entry, exc_info=None):
100        """This is used to confirm that everything is working as it should."""
101        self.logger.info(str(entry), exc_info=exc_info)
102
103    def write_warning_to_report(self, entry, exc_info=None):
104        """Something unexpected has happened or some problem is about to happen in the near future."""
105        self.logger.warning(str(entry), exc_info=exc_info)
106
107    def write_error_to_report(self, entry, exc_info=None):
108        """An error has occurred. The software was unable to perform some function."""
109        self.logger.error(str(entry), exc_info=exc_info)
110
111    def write_critical_to_report(self, entry, exc_info=None):
112        """A serious error has occurred. We may shut down or not be able to continue running properly."""
113        self.logger.critical(str(entry), exc_info=exc_info)
114
115    def write_result_to_html_report(self, text: str):
116        """Add text to the result section of the current test."""
117        self.logger.info(text)
118        self._html_result_text.append(text)
119
120    def write_warning_to_html_report(self, text: str):
121        """Add warning text to the result section of the current test."""
122        self.logger.warning(text)
123        self._html_result_text.append(f'<font color="#ca6500">⚠️ {text} </font>')
124
125    def write_failure_to_html_report(self, text: str):
126        """Add failing error text to the result section of the current test."""
127        self.logger.warning(text)
128        self._html_result_text.append(f'<font color="#990000">{text}</font>')
129
130    @property
131    def html_result_text(self) -> list[str]:
132        """Fetch and clear html text."""
133        result = self._html_result_text[:]
134        del self._html_result_text[:]
135        return result

Provides logging features for the HITL.

Logger(log_level: int = 10)
53    def __init__(self, log_level: int = logging.DEBUG):
54        """Create the logger object."""
55        self.logger = logging.getLogger("HITL")
56        self.logger.setLevel(log_level)
57        self._html_result_text: list[str] = []
58        self.log_file: Path = Path()
59
60        # Silence other modules
61        for logger_name in ("pyvisa", "asyncio", "can"):
62            logging.getLogger(logger_name).setLevel(logging.WARNING)

Create the logger object.

instance: ClassVar[Optional[Self]] = <Logger object>
logger
log_file: pathlib.Path
def use_file_mode(self, report_filename: pathlib.Path, log_level: int = 10):
64    def use_file_mode(self, report_filename: Path, log_level: int = logging.DEBUG):
65        """Switch to a file based log"""
66        self.logger.setLevel(log_level)
67
68        class NoANSIFormatter(logging.Formatter):
69            """Strips ANSI escape codes from the log message."""
70
71            def format(self, record):
72                """Return logger message with terminal escapes removed."""
73                record.msg = re.sub(r"\x1b\[[0-9;]*m", "", record.msg)
74                return super().format(record)
75
76        formatter = NoANSIFormatter(
77            "%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%m/%d/%Y %I:%M:%S%p"
78        )
79
80        # Setup the date part of the filename
81        self.log_file = Path(f"{report_filename}.txt")
82
83        file_log_handler = logging.FileHandler(self.log_file)
84        file_log_handler.setLevel(self.logger.level)
85        file_log_handler.setFormatter(formatter)
86        self.logger.addHandler(file_log_handler)
87        self.write_debug_to_report(f"Log Filename: {self.log_file}")

Switch to a file based log

def write_result_to_report(self, result, exc_info=None):
91    def write_result_to_report(self, result, exc_info=None):
92        """Provide an interface to allow test cases to write test results to the test report."""
93        self.logger.info(f"Test Result - {result}", exc_info=exc_info)

Provide an interface to allow test cases to write test results to the test report.

def write_debug_to_report(self, entry, exc_info=None):
95    def write_debug_to_report(self, entry, exc_info=None):
96        """Detailed information, useful only when a problem is being diagnosed."""
97        self.logger.debug(str(entry), exc_info=exc_info)

Detailed information, useful only when a problem is being diagnosed.

def write_info_to_report(self, entry, exc_info=None):
 99    def write_info_to_report(self, entry, exc_info=None):
100        """This is used to confirm that everything is working as it should."""
101        self.logger.info(str(entry), exc_info=exc_info)

This is used to confirm that everything is working as it should.

def write_warning_to_report(self, entry, exc_info=None):
103    def write_warning_to_report(self, entry, exc_info=None):
104        """Something unexpected has happened or some problem is about to happen in the near future."""
105        self.logger.warning(str(entry), exc_info=exc_info)

Something unexpected has happened or some problem is about to happen in the near future.

def write_error_to_report(self, entry, exc_info=None):
107    def write_error_to_report(self, entry, exc_info=None):
108        """An error has occurred. The software was unable to perform some function."""
109        self.logger.error(str(entry), exc_info=exc_info)

An error has occurred. The software was unable to perform some function.

def write_critical_to_report(self, entry, exc_info=None):
111    def write_critical_to_report(self, entry, exc_info=None):
112        """A serious error has occurred. We may shut down or not be able to continue running properly."""
113        self.logger.critical(str(entry), exc_info=exc_info)

A serious error has occurred. We may shut down or not be able to continue running properly.

def write_result_to_html_report(self, text: str):
115    def write_result_to_html_report(self, text: str):
116        """Add text to the result section of the current test."""
117        self.logger.info(text)
118        self._html_result_text.append(text)

Add text to the result section of the current test.

def write_warning_to_html_report(self, text: str):
120    def write_warning_to_html_report(self, text: str):
121        """Add warning text to the result section of the current test."""
122        self.logger.warning(text)
123        self._html_result_text.append(f'<font color="#ca6500">⚠️ {text} </font>')

Add warning text to the result section of the current test.

def write_failure_to_html_report(self, text: str):
125    def write_failure_to_html_report(self, text: str):
126        """Add failing error text to the result section of the current test."""
127        self.logger.warning(text)
128        self._html_result_text.append(f'<font color="#990000">{text}</font>')

Add failing error text to the result section of the current test.

html_result_text: list[str]
130    @property
131    def html_result_text(self) -> list[str]:
132        """Fetch and clear html text."""
133        result = self._html_result_text[:]
134        del self._html_result_text[:]
135        return result

Fetch and clear html text.

logger = <Logger object>