1#!/usr/bin/env python3
2#
3#   Copyright 2018 - 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
17import logging
18import re
19
20from acts.libs.proc.process import Process
21from acts.libs.logging import log_stream
22from acts.libs.logging.log_stream import LogStyles
23
24TIMESTAMP_REGEX = r'((?:\d+-)?\d+-\d+ \d+:\d+:\d+.\d+)'
25
26
27class TimestampTracker(object):
28    """Stores the last timestamp outputted by the Logcat process."""
29
30    def __init__(self):
31        self._last_timestamp = None
32
33    @property
34    def last_timestamp(self):
35        return self._last_timestamp
36
37    def read_output(self, message):
38        """Reads the message and parses all timestamps from it."""
39        all_timestamps = re.findall(TIMESTAMP_REGEX, message)
40        if len(all_timestamps) > 0:
41            self._last_timestamp = all_timestamps[0]
42
43
44def _get_log_level(message):
45    """Returns the log level for the given message."""
46    if message.startswith('-') or len(message) < 37:
47        return logging.ERROR
48    else:
49        log_level = message[36]
50        if log_level in ('V', 'D'):
51            return logging.DEBUG
52        elif log_level == 'I':
53            return logging.INFO
54        elif log_level == 'W':
55            return logging.WARNING
56        elif log_level == 'E':
57            return logging.ERROR
58    return logging.NOTSET
59
60
61def _log_line_func(log, timestamp_tracker):
62    """Returns a lambda that logs a message to the given logger."""
63
64    def log_line(message):
65        timestamp_tracker.read_output(message)
66        log.log(_get_log_level(message), message)
67
68    return log_line
69
70
71def _on_retry(serial, extra_params, timestamp_tracker):
72    def on_retry(_):
73        begin_at = '"%s"' % (timestamp_tracker.last_timestamp or 1)
74        additional_params = extra_params or ''
75
76        return 'adb -s %s logcat -T %s -v year %s' % (
77            serial, begin_at, additional_params)
78
79    return on_retry
80
81
82def create_logcat_keepalive_process(serial, logcat_dir, extra_params=''):
83    """Creates a Logcat Process that automatically attempts to reconnect.
84
85    Args:
86        serial: The serial of the device to read the logcat of.
87        logcat_dir: The directory used for logcat file output.
88        extra_params: Any additional params to be added to the logcat cmdline.
89
90    Returns:
91        A acts.libs.proc.process.Process object.
92    """
93    logger = log_stream.create_logger(
94        'adblog_%s' % serial, log_name=serial, subcontext=logcat_dir,
95        log_styles=(LogStyles.LOG_DEBUG | LogStyles.TESTCASE_LOG))
96    process = Process('adb -s %s logcat -T 1 -v year %s' %
97                      (serial, extra_params))
98    timestamp_tracker = TimestampTracker()
99    process.set_on_output_callback(_log_line_func(logger, timestamp_tracker))
100    process.set_on_terminate_callback(
101        _on_retry(serial, extra_params, timestamp_tracker))
102    return process
103