| '''Initialise the Python logging facility for the test suite. |
| |
| from __future__ import absolute_import |
| |
| It provides the function to initialise the logging facility and retrieve an |
| instance of the logger class. It also contains the definition of the internal |
| logger class. |
| ''' |
| from __future__ import print_function |
| |
| import io |
| import sys |
| import logging |
| |
| |
| INITIALISED = False |
| NAMESPACE = 'RS_LLDB_TESTSUITE' |
| |
| def initialise(identifier, level=logging.INFO, print_to_stdout=False, |
| file_path=None, file_mode='a'): |
| '''Initialise the logging facility for the test suite. |
| |
| This function should be invoked only once, at the start of the program, and |
| before emitting any log. |
| |
| Args: |
| identifier: String, a label that will be part of each record. It is |
| usually the test case name. |
| level: Integer, all messages above this log level will be discarded. |
| Valid values are those recognised by the python logging module: |
| https://docs.python.org/2/library/logging.html#levels . |
| print_to_stdout: Boolean, whether the logs should be redirected to |
| sys.stdout (true) or stored into a text file (false). |
| file_path: String, path to the text file in which to store the logs. |
| This option is only meaningful when print_to_stdout = False. |
| file_mode: String, the mode to open the text file. Valid modes are |
| those recognised by the standard Python `open' function. |
| This option is only meaningful when print_to_stdout = False. |
| |
| Raises: |
| RuntimeError: If the logging has already been initialised |
| ValueError: If the argument "file_path" has not been provided when |
| print_to_stdout=False |
| ''' |
| # pylint: disable=global-statement |
| global INITIALISED |
| if INITIALISED: |
| raise RuntimeError('Already initialised') |
| |
| # set the logging class |
| old_logger_class = logging.getLoggerClass() |
| logging.setLoggerClass(RsLogger) |
| |
| # initialise the Logger |
| log = logging.getLogger(NAMESPACE) |
| log.setLevel(level) # reject all logs below |
| |
| # don't propagate the log records to the logging root |
| log.propagate = False |
| |
| # restore the previous class |
| logging.setLoggerClass(old_logger_class) |
| |
| # handler |
| if print_to_stdout: |
| handler_default = logging.StreamHandler(sys.stdout) |
| else: |
| if file_path is None: |
| raise ValueError('Missing mandatory argument "file_path"') |
| |
| handler_default = logging.FileHandler(file_path, file_mode) |
| |
| # Do not filter records in the handler because of the level |
| handler_default.setLevel(logging.NOTSET) |
| |
| # format the message |
| handler_default.setFormatter( |
| logging.Formatter( |
| '%(asctime)s [{0}] [%(levelname)s] %(message)s' |
| .format(identifier) |
| )) |
| |
| log.addHandler(handler_default) |
| |
| INITIALISED = True |
| |
| |
| class RsLogger(logging.getLoggerClass()): |
| '''Internal logging class. |
| |
| This is an internal class to enhance the logging facility with the methods |
| "log_and_print" and "seek_to_end". |
| ''' |
| # pylint: disable=too-many-public-methods |
| |
| def log_and_print(self, msg, level=logging.INFO): |
| '''Print "msg" to stdout and emit a log record. |
| |
| Args: |
| msg: The message to emit. |
| level: The level to use. By default it is logging.INFO. |
| ''' |
| print(msg) |
| self.log(level, msg) |
| |
| def seek_to_end(self): |
| '''Reset the cursor position to the end for all handlers that are |
| Text File managers.''' |
| for hndlr in self.handlers: |
| if isinstance(hndlr, logging.FileHandler): |
| hndlr.stream.seek(0, io.SEEK_END) |
| |
| |
| def get_logger(): |
| '''Retrieves the Logger instance related to the testsuite. |
| |
| Throws: |
| RuntimeError: If the logging facility has not been initialised with |
| "initialise" beforehand. |
| |
| Returns: |
| An instance of logging.Logger to write the logs. |
| ''' |
| if not INITIALISED: |
| raise RuntimeError('Logging facility not initialised') |
| |
| return logging.getLogger(NAMESPACE) |