1#!/usr/bin/env python3.4 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from __future__ import print_function 18 19import datetime 20import logging 21import os 22import re 23import sys 24 25from acts.utils import create_dir 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 52def is_valid_logline_timestamp(timestamp): 53 if len(timestamp) == log_line_timestamp_len: 54 if logline_timestamp_re.match(timestamp): 55 return True 56 return False 57 58def logline_timestamp_comparator(t1, t2): 59 """Comparator for timestamps in logline format. 60 61 Args: 62 t1: Timestamp in logline format. 63 t2: Timestamp in logline format. 64 65 Returns: 66 -1 if t1 < t2; 1 if t1 > t2; 0 if t1 == t2. 67 """ 68 dt1 = _parse_logline_timestamp(t1) 69 dt2 = _parse_logline_timestamp(t2) 70 for u1, u2 in zip(dt1, dt2): 71 if u1 < u2: 72 return -1 73 elif u1 > u2: 74 return 1 75 return 0 76 77def _get_timestamp(time_format, delta=None): 78 t = datetime.datetime.now() 79 if delta: 80 t = t + datetime.timedelta(seconds=delta) 81 return t.strftime(time_format)[:-3] 82 83def epoch_to_log_line_timestamp(epoch_time): 84 d = datetime.datetime.fromtimestamp(epoch_time / 1000) 85 return d.strftime("%m-%d %H:%M:%S.%f")[:-3] 86 87def get_log_line_timestamp(delta=None): 88 """Returns a timestamp in the format used by log lines. 89 90 Default is current time. If a delta is set, the return value will be 91 the current time offset by delta seconds. 92 93 Args: 94 delta: Number of seconds to offset from current time; can be negative. 95 96 Returns: 97 A timestamp in log line format with an offset. 98 """ 99 return _get_timestamp("%m-%d %H:%M:%S.%f", delta) 100 101def get_log_file_timestamp(delta=None): 102 """Returns a timestamp in the format used for log file names. 103 104 Default is current time. If a delta is set, the return value will be 105 the current time offset by delta seconds. 106 107 Args: 108 delta: Number of seconds to offset from current time; can be negative. 109 110 Returns: 111 A timestamp in log filen name format with an offset. 112 """ 113 return _get_timestamp("%m-%d-%Y_%H-%M-%S-%f", delta) 114 115def _get_test_logger(log_path, TAG, prefix=None, filename=None): 116 """Returns a logger object used for tests. 117 118 The logger object has a stream handler and a file handler. The stream 119 handler logs INFO level to the terminal, the file handler logs DEBUG 120 level to files. 121 122 Args: 123 log_path: Location of the log file. 124 TAG: Name of the logger's owner. 125 prefix: A prefix for each log line in terminal. 126 filename: Name of the log file. The default is the time the logger 127 is requested. 128 129 Returns: 130 A logger configured with one stream handler and one file handler 131 """ 132 log = logging.getLogger(TAG) 133 if log.handlers: 134 # This logger has been requested before. 135 return log 136 log.propagate = False 137 log.setLevel(logging.DEBUG) 138 # Log info to stream 139 terminal_format = log_line_format 140 if prefix: 141 terminal_format = "[{}] {}".format(prefix, log_line_format) 142 c_formatter = logging.Formatter(terminal_format, log_line_time_format) 143 ch = logging.StreamHandler(sys.stdout) 144 ch.setFormatter(c_formatter) 145 ch.setLevel(logging.INFO) 146 # Log everything to file 147 f_formatter = logging.Formatter(log_line_format, log_line_time_format) 148 # All the logs of this test class go into one directory 149 if filename is None: 150 filename = get_log_file_timestamp() 151 create_dir(log_path) 152 fh = logging.FileHandler(os.path.join(log_path, 'test_run_details.txt')) 153 fh.setFormatter(f_formatter) 154 fh.setLevel(logging.DEBUG) 155 log.addHandler(ch) 156 log.addHandler(fh) 157 log.log_path = log_path 158 return log 159 160def kill_test_logger(logger): 161 """Cleans up a test logger object created by get_test_logger. 162 163 Args: 164 logger: The logging object to clean up. 165 """ 166 for h in list(logger.handlers): 167 logger.removeHandler(h) 168 if isinstance(h, logging.FileHandler): 169 h.close() 170 171def create_latest_log_alias(actual_path): 172 """Creates a symlink to the latest test run logs. 173 174 Args: 175 actual_path: The source directory where the latest test run's logs are. 176 """ 177 link_path = os.path.join(os.path.dirname(actual_path), "latest") 178 if os.path.islink(link_path): 179 os.remove(link_path) 180 os.symlink(actual_path, link_path) 181 182def get_test_logger(log_path, TAG, prefix=None, filename=None): 183 """Returns a logger customized for a test run. 184 185 Args: 186 log_path: Location of the report file. 187 TAG: Name of the logger's owner. 188 prefix: A prefix for each log line in terminal. 189 filename: Name of the files. The default is the time the objects 190 are requested. 191 192 Returns: 193 A logger object. 194 """ 195 if filename is None: 196 filename = get_log_file_timestamp() 197 create_dir(log_path) 198 logger = _get_test_logger(log_path, TAG, prefix, filename) 199 create_latest_log_alias(log_path) 200 return logger 201 202def normalize_log_line_timestamp(log_line_timestamp): 203 """Replace special characters in log line timestamp with normal characters. 204 205 Args: 206 log_line_timestamp: A string in the log line timestamp format. Obtained 207 with get_log_line_timestamp. 208 209 Returns: 210 A string representing the same time as input timestamp, but without 211 special characters. 212 """ 213 norm_tp = log_line_timestamp.replace(' ', '_') 214 norm_tp = norm_tp.replace(':', '-') 215 return norm_tp 216 217class LoggerProxy(object): 218 """This class is for situations where a logger may or may not exist. 219 220 e.g. In controller classes, sometimes we don't have a logger to pass in, 221 like during a quick try in python console. In these cases, we don't want to 222 crash on the log lines because logger is None, so we should set self.log to 223 an object of this class in the controller classes, instead of the actual 224 logger object. 225 """ 226 def __init__(self, logger=None): 227 self.log = logger 228 229 @property 230 def log_path(self): 231 if self.log: 232 return self.log.log_path 233 return "/tmp/logs" 234 235 def __getattr__(self, name): 236 def log_call(*args): 237 if self.log: 238 return getattr(self.log, name)(*args) 239 print(*args) 240 return log_call