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