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
17from __future__ import absolute_import
18
19It provides the function to initialise the logging facility and retrieve an
20instance of the logger class. It also contains the definition of the internal
21logger class.
22'''
23from __future__ import print_function
24
25import io
26import sys
27import logging
28
29
30INITIALISED = False
31NAMESPACE = 'RS_LLDB_TESTSUITE'
32
33def 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
102class 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
128def 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