• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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