Home | History | Annotate | Download | only in harness
      1 # Copyright (C) 2016 The Android Open Source Project
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #      http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 '''Initialise the Python logging facility for the test suite.
     16 
     17 from __future__ import absolute_import
     18 
     19 It provides the function to initialise the logging facility and retrieve an
     20 instance of the logger class. It also contains the definition of the internal
     21 logger class.
     22 '''
     23 from __future__ import print_function
     24 
     25 import io
     26 import sys
     27 import logging
     28 
     29 
     30 INITIALISED = False
     31 NAMESPACE = 'RS_LLDB_TESTSUITE'
     32 
     33 def initialise(identifier, level=logging.INFO, print_to_stdout=False,
     34                file_path=None, file_mode='a'):
     35     '''Initialise the logging facility for the test suite.
     36 
     37     This function should be invoked only once, at the start of the program, and
     38     before emitting any log.
     39 
     40     Args:
     41         identifier: String, a label that will be part of each record. It is
     42                     usually the test case name.
     43         level: Integer, all messages above this log level will be discarded.
     44                Valid values are those recognised by the python logging module:
     45                https://docs.python.org/2/library/logging.html#levels .
     46         print_to_stdout: Boolean, whether the logs should be redirected to
     47                          sys.stdout (true) or stored into a text file (false).
     48         file_path: String, path to the text file in which to store the logs.
     49                    This option is only meaningful when print_to_stdout = False.
     50         file_mode: String, the mode to open the text file. Valid modes are
     51                    those recognised by the standard Python `open' function.
     52                    This option is only meaningful when print_to_stdout = False.
     53 
     54     Raises:
     55         RuntimeError: If the logging has already been initialised
     56         ValueError: If the argument "file_path" has not been provided when
     57                     print_to_stdout=False
     58     '''
     59     # pylint: disable=global-statement
     60     global INITIALISED
     61     if INITIALISED:
     62         raise RuntimeError('Already initialised')
     63 
     64     # set the logging class
     65     old_logger_class = logging.getLoggerClass()
     66     logging.setLoggerClass(RsLogger)
     67 
     68     # initialise the Logger
     69     log = logging.getLogger(NAMESPACE)
     70     log.setLevel(level) # reject all logs below
     71 
     72     # don't propagate the log records to the logging root
     73     log.propagate = False
     74 
     75     # restore the previous class
     76     logging.setLoggerClass(old_logger_class)
     77 
     78     # handler
     79     if print_to_stdout:
     80         handler_default = logging.StreamHandler(sys.stdout)
     81     else:
     82         if file_path is None:
     83             raise ValueError('Missing mandatory argument "file_path"')
     84 
     85         handler_default = logging.FileHandler(file_path, file_mode)
     86 
     87     # Do not filter records in the handler because of the level
     88     handler_default.setLevel(logging.NOTSET)
     89 
     90     # format the message
     91     handler_default.setFormatter(
     92         logging.Formatter(
     93             '%(asctime)s [{0}] [%(levelname)s] %(message)s'
     94                 .format(identifier)
     95     ))
     96 
     97     log.addHandler(handler_default)
     98 
     99     INITIALISED = True
    100 
    101 
    102 class RsLogger(logging.getLoggerClass()):
    103     '''Internal logging class.
    104 
    105     This is an internal class to enhance the logging facility with the methods
    106     "log_and_print" and "seek_to_end".
    107     '''
    108     # pylint: disable=too-many-public-methods
    109 
    110     def log_and_print(self, msg, level=logging.INFO):
    111         '''Print "msg" to stdout and emit a log record.
    112 
    113         Args:
    114             msg: The message to emit.
    115             level: The level to use. By default it is logging.INFO.
    116         '''
    117         print(msg)
    118         self.log(level, msg)
    119 
    120     def seek_to_end(self):
    121         '''Reset the cursor position to the end for all handlers that are
    122         Text File managers.'''
    123         for hndlr in self.handlers:
    124             if isinstance(hndlr, logging.FileHandler):
    125                 hndlr.stream.seek(0, io.SEEK_END)
    126 
    127 
    128 def get_logger():
    129     '''Retrieves the Logger instance related to the testsuite.
    130 
    131     Throws:
    132         RuntimeError: If the logging facility has not been initialised with
    133                       "initialise" beforehand.
    134 
    135     Returns:
    136         An instance of logging.Logger to write the logs.
    137     '''
    138     if not INITIALISED:
    139         raise RuntimeError('Logging facility not initialised')
    140 
    141     return logging.getLogger(NAMESPACE)
    142