• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17from __future__ import print_function
18
19import datetime
20import logging
21import os
22import re
23import sys
24
25from vts.runners.host import utils
26
27log_line_format = "%(asctime)s.%(msecs).03d %(levelname)s %(message)s"
28# The micro seconds are added by the format string above,
29# so the time format does not include ms.
30log_line_time_format = "%m-%d %H:%M:%S"
31log_line_timestamp_len = 18
32
33logline_timestamp_re = re.compile("\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d")
34
35
36def _parse_logline_timestamp(t):
37    """Parses a logline timestamp into a tuple.
38
39    Args:
40        t: Timestamp in logline format.
41
42    Returns:
43        An iterable of date and time elements in the order of month, day, hour,
44        minute, second, microsecond.
45    """
46    date, time = t.split(' ')
47    month, day = date.split('-')
48    h, m, s = time.split(':')
49    s, ms = s.split('.')
50    return (month, day, h, m, s, ms)
51
52
53def isValidLogLineTimestamp(timestamp):
54    if len(timestamp) == log_line_timestamp_len:
55        if logline_timestamp_re.match(timestamp):
56            return True
57    return False
58
59
60def logLineTimestampComparator(t1, t2):
61    """Comparator for timestamps in logline format.
62
63    Args:
64        t1: Timestamp in logline format.
65        t2: Timestamp in logline format.
66
67    Returns:
68        -1 if t1 < t2; 1 if t1 > t2; 0 if t1 == t2.
69    """
70    dt1 = _parse_logline_timestamp(t1)
71    dt2 = _parse_logline_timestamp(t2)
72    for u1, u2 in zip(dt1, dt2):
73        if u1 < u2:
74            return -1
75        elif u1 > u2:
76            return 1
77    return 0
78
79
80def _get_timestamp(time_format, delta=None):
81    t = datetime.datetime.now()
82    if delta:
83        t = t + datetime.timedelta(seconds=delta)
84    return t.strftime(time_format)[:-3]
85
86
87def epochToLogLineTimestamp(epoch_time):
88    d = datetime.datetime.fromtimestamp(epoch_time / 1000)
89    return d.strftime("%m-%d %H:%M:%S.%f")[:-3]
90
91
92def getLogLineTimestamp(delta=None):
93    """Returns a timestamp in the format used by log lines.
94
95    Default is current time. If a delta is set, the return value will be
96    the current time offset by delta seconds.
97
98    Args:
99        delta: Number of seconds to offset from current time; can be negative.
100
101    Returns:
102        A timestamp in log line format with an offset.
103    """
104    return _get_timestamp("%m-%d %H:%M:%S.%f", delta)
105
106
107def getLogFileTimestamp(delta=None):
108    """Returns a timestamp in the format used for log file names.
109
110    Default is current time. If a delta is set, the return value will be
111    the current time offset by delta seconds.
112
113    Args:
114        delta: Number of seconds to offset from current time; can be negative.
115
116    Returns:
117        A timestamp in log filen name format with an offset.
118    """
119    return _get_timestamp("%m-%d-%Y_%H-%M-%S-%f", delta)
120
121
122def _initiateTestLogger(log_path, prefix=None, filename=None):
123    """Customizes the root logger for a test run.
124
125    The logger object has a stream handler and a file handler. The stream
126    handler logs INFO level to the terminal, the file handler logs DEBUG
127    level to files.
128
129    Args:
130        log_path: Location of the log file.
131        prefix: A prefix for each log line in terminal.
132        filename: Name of the log file. The default is the time the logger
133                  is requested.
134    """
135    log = logging.getLogger()
136    # Clean up any remaining handlers.
137    killTestLogger(log)
138    log.propagate = False
139    log.setLevel(logging.DEBUG)
140    # Log info to stream
141    terminal_format = log_line_format
142    if prefix:
143        terminal_format = "[{}] {}".format(prefix, log_line_format)
144    c_formatter = logging.Formatter(terminal_format, log_line_time_format)
145    ch = logging.StreamHandler(sys.stdout)
146    ch.setFormatter(c_formatter)
147    ch.setLevel(logging.INFO)
148    # Log everything to file
149    f_formatter = logging.Formatter(log_line_format, log_line_time_format)
150    # All the logs of this test class go into one directory
151    if filename is None:
152        filename = getLogFileTimestamp()
153        utils.create_dir(log_path)
154    fh = logging.FileHandler(os.path.join(log_path, 'test_run_details.txt'))
155    fh.setFormatter(f_formatter)
156    fh.setLevel(logging.DEBUG)
157    log.addHandler(ch)
158    log.addHandler(fh)
159    log.log_path = log_path
160    logging.log_path = log_path
161
162
163def killTestLogger(logger):
164    """Cleans up the handlers attached to a test logger object.
165
166    Args:
167        logger: The logging object to clean up.
168    """
169    for h in list(logger.handlers):
170        if isinstance(h, logging.FileHandler):
171            h.close()
172        logger.removeHandler(h)
173
174
175def isSymlinkSupported():
176    """Checks whether the OS supports symbolic link.
177
178    Returns:
179        A boolean representing whether the OS supports symbolic link.
180    """
181    return hasattr(os, "symlink")
182
183
184def createLatestLogAlias(actual_path):
185    """Creates a symlink to the latest test run logs.
186
187    Args:
188        actual_path: The source directory where the latest test run's logs are.
189    """
190    link_path = os.path.join(os.path.dirname(actual_path), "latest")
191    if os.path.islink(link_path):
192        os.remove(link_path)
193    os.symlink(actual_path, link_path)
194
195
196def setupTestLogger(log_path, prefix=None, filename=None):
197    """Customizes the root logger for a test run.
198
199    Args:
200        log_path: Location of the report file.
201        prefix: A prefix for each log line in terminal.
202        filename: Name of the files. The default is the time the objects
203            are requested.
204    """
205    if filename is None:
206        filename = getLogFileTimestamp()
207    utils.create_dir(log_path)
208    logger = _initiateTestLogger(log_path, prefix, filename)
209    if isSymlinkSupported():
210        createLatestLogAlias(log_path)
211
212
213def normalizeLogLineTimestamp(log_line_timestamp):
214    """Replace special characters in log line timestamp with normal characters.
215
216    Args:
217        log_line_timestamp: A string in the log line timestamp format. Obtained
218            with getLogLineTimestamp.
219
220    Returns:
221        A string representing the same time as input timestamp, but without
222        special characters.
223    """
224    norm_tp = log_line_timestamp.replace(' ', '_')
225    norm_tp = norm_tp.replace(':', '-')
226    return norm_tp
227
228
229class LoggerProxy(object):
230    """This class is for situations where a logger may or may not exist.
231
232    e.g. In controller classes, sometimes we don't have a logger to pass in,
233    like during a quick try in python console. In these cases, we don't want to
234    crash on the log lines because logger is None, so we should set self.log to
235    an object of this class in the controller classes, instead of the actual
236    logger object.
237    """
238
239    def __init__(self, logger=None):
240        self.log = logger
241
242    @property
243    def log_path(self):
244        if self.log:
245            return self.log.log_path
246        return "/tmp/logs"
247
248    def __getattr__(self, name):
249        def log_call(*args):
250            if self.log:
251                return getattr(self.log, name)(*args)
252            print(*args)
253
254        return log_call
255