1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 # pylint: disable=unused-argument
16 from devil
.android
import decorators
17 from devil
.android
import device_errors
18 from devil
.android
.sdk
import adb_wrapper
19 from devil
.utils
import reraiser_thread
21 logger
= logging
.getLogger(__name__
)
24 class LogcatMonitor(object):
26 _RECORD_ITER_TIMEOUT
= 2.0
27 _RECORD_THREAD_JOIN_WAIT
= 5.0
29 _THREADTIME_RE_FORMAT
= (
30 r
'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +'
31 r
'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$')
33 def __init__(self
, adb
, clear
=True, filter_specs
=None, output_file
=None):
34 """Create a LogcatMonitor instance.
37 adb: An instance of adb_wrapper.AdbWrapper.
38 clear: If True, clear the logcat when monitoring starts.
39 filter_specs: An optional list of '<tag>[:priority]' strings.
40 output_file: File path to save recorded logcat.
42 if isinstance(adb
, adb_wrapper
.AdbWrapper
):
45 raise ValueError('Unsupported type passed for argument "device"')
47 self
._filter
_specs
= filter_specs
48 self
._output
_file
= output_file
49 self
._record
_file
= None
50 self
._record
_file
_lock
= threading
.Lock()
51 self
._record
_thread
= None
52 self
._stop
_recording
_event
= threading
.Event()
55 def output_file(self
):
56 return self
._output
_file
58 @decorators.WithTimeoutAndRetriesDefaults(10, 0)
59 def WaitFor(self
, success_regex
, failure_regex
=None, timeout
=None,
61 """Wait for a matching logcat line or until a timeout occurs.
63 This will attempt to match lines in the logcat against both |success_regex|
64 and |failure_regex| (if provided). Note that this calls re.search on each
65 logcat line, not re.match, so the provided regular expressions don't have
66 to match an entire line.
69 success_regex: The regular expression to search for.
70 failure_regex: An optional regular expression that, if hit, causes this
71 to stop looking for a match. Can be None.
72 timeout: timeout in seconds
73 retries: number of retries
76 A match object if |success_regex| matches a part of a logcat line, or
77 None if |failure_regex| matches a part of a logcat line.
79 CommandFailedError on logcat failure (NOT on a |failure_regex| match).
80 CommandTimeoutError if no logcat line matching either |success_regex| or
81 |failure_regex| is found in |timeout| seconds.
82 DeviceUnreachableError if the device becomes unreachable.
83 LogcatMonitorCommandError when calling |WaitFor| while not recording
86 if self
._record
_thread
is None:
87 raise LogcatMonitorCommandError(
88 'Must be recording logcat when calling |WaitFor|',
89 device_serial
=str(self
._adb
))
90 if isinstance(success_regex
, basestring
):
91 success_regex
= re
.compile(success_regex
)
92 if isinstance(failure_regex
, basestring
):
93 failure_regex
= re
.compile(failure_regex
)
95 logger
.debug('Waiting %d seconds for "%s"', timeout
, success_regex
.pattern
)
97 # NOTE This will continue looping until:
98 # - success_regex matches a line, in which case the match object is
100 # - failure_regex matches a line, in which case None is returned
101 # - the timeout is hit, in which case a CommandTimeoutError is raised.
102 with
open(self
._record
_file
.name
, 'r') as f
:
106 m
= success_regex
.search(line
)
109 if failure_regex
and failure_regex
.search(line
):
112 time
.sleep(self
._WAIT
_TIME
)
114 def FindAll(self
, message_regex
, proc_id
=None, thread_id
=None, log_level
=None,
116 """Finds all lines in the logcat that match the provided constraints.
119 message_regex: The regular expression that the <message> section must
121 proc_id: The process ID to match. If None, matches any process ID.
122 thread_id: The thread ID to match. If None, matches any thread ID.
123 log_level: The log level to match. If None, matches any log level.
124 component: The component to match. If None, matches any component.
127 LogcatMonitorCommandError when calling |FindAll| before recording logcat.
130 A match object for each matching line in the logcat. The match object
131 will always contain, in addition to groups defined in |message_regex|,
132 the following named groups: 'date', 'time', 'proc_id', 'thread_id',
133 'log_level', 'component', and 'message'.
135 if self
._record
_file
is None:
136 raise LogcatMonitorCommandError(
137 'Must have recorded or be recording a logcat to call |FindAll|',
138 device_serial
=str(self
._adb
))
141 if thread_id
is None:
143 if log_level
is None:
144 log_level
= r
'[VDIWEF]'
145 if component
is None:
146 component
= r
'[^\s:]+'
147 # pylint: disable=protected-access
148 threadtime_re
= re
.compile(
149 type(self
)._THREADTIME
_RE
_FORMAT
% (
150 proc_id
, thread_id
, log_level
, component
, message_regex
))
152 with
open(self
._record
_file
.name
, 'r') as f
:
154 m
= re
.match(threadtime_re
, line
)
158 def _StartRecording(self
):
159 """Starts recording logcat to file.
161 Function spawns a thread that records logcat to file and will not die
162 until |StopRecording| is called.
164 def record_to_file():
165 # Write the log with line buffering so the consumer sees each individual
167 for data
in self
._adb
.Logcat(filter_specs
=self
._filter
_specs
,
168 logcat_format
='threadtime',
169 iter_timeout
=self
._RECORD
_ITER
_TIMEOUT
):
170 if self
._stop
_recording
_event
.isSet():
174 # Logcat can yield None if the iter_timeout is hit.
177 with self
._record
_file
_lock
:
178 if self
._record
_file
and not self
._record
_file
.closed
:
179 self
._record
_file
.write(data
+ '\n')
181 self
._stop
_recording
_event
.clear()
182 if not self
._record
_thread
:
183 self
._record
_thread
= reraiser_thread
.ReraiserThread(record_to_file
)
184 self
._record
_thread
.start()
186 def _StopRecording(self
):
187 """Finish recording logcat."""
188 if self
._record
_thread
:
189 self
._stop
_recording
_event
.set()
190 self
._record
_thread
.join(timeout
=self
._RECORD
_THREAD
_JOIN
_WAIT
)
191 self
._record
_thread
.ReraiseIfException()
192 self
._record
_thread
= None
195 """Starts the logcat monitor.
197 Clears the logcat if |clear| was set in |__init__|.
200 self
._adb
.Logcat(clear
=True)
201 if not self
._record
_file
:
202 self
._record
_file
= tempfile
.NamedTemporaryFile(mode
='a', bufsize
=1)
203 self
._StartRecording
()
206 """Stops the logcat monitor.
208 Stops recording the logcat. Copies currently recorded logcat to
211 self
._StopRecording
()
212 with self
._record
_file
_lock
:
213 if self
._record
_file
and self
._output
_file
:
215 os
.makedirs(os
.path
.dirname(self
._output
_file
))
217 if e
.errno
!= errno
.EEXIST
:
219 shutil
.copy(self
._record
_file
.name
, self
._output
_file
)
222 """Closes logcat recording file.
224 Should be called when finished using the logcat monitor.
226 with self
._record
_file
_lock
:
227 if self
._record
_file
:
228 self
._record
_file
.close()
229 self
._record
_file
= None
232 """Starts the logcat monitor."""
236 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
237 """Stops the logcat monitor."""
241 """Closes logcat recording file in case |Close| was never called."""
242 with self
._record
_file
_lock
:
243 if self
._record
_file
:
245 'Need to call |Close| on the logcat monitor when done!')
246 self
._record
_file
.close()
253 class LogcatMonitorCommandError(device_errors
.CommandFailedError
):
254 """Exception for errors with logcat monitor commands."""