blob: 649c70ea32109b35f20e3f35d9894e1a409ab17c [file] [log] [blame]
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Eric Lie7c4cab2011-01-05 14:39:19 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mark Koudritsky1752f9f2012-05-16 17:12:14 -04005import logging, os, re, time
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02006
Eric Lie7c4cab2011-01-05 14:39:19 -08007import common
Eric Lie7c4cab2011-01-05 14:39:19 -08008from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import error
Eric Carusoa4ac7a82015-08-06 10:53:54 -070010from autotest_lib.client.common_lib.cros.constants import CLEANUP_LOGS_PAUSED_FILE
Eric Lie7c4cab2011-01-05 14:39:19 -080011
Mark Koudritsky1752f9f2012-05-16 17:12:14 -040012
Julius Wernera825d252012-12-17 16:22:06 -080013def strip_timestamp(msg):
14 """
15 Strips timestamps and PIDs from a syslog message to facilitate
16 failure reason aggregation when this message is used as an autotest
17 exception text.
18 """
19 kernel = re.search(r' kernel: \[ *\d+.\d+\] (.*)$', msg)
20 if kernel:
21 return 'kernel: ' + kernel.group(1)
22
23 user = re.match(r'[^a-z]+ [^ ]* ([^\[ ]+)(?:\[.*\])?(: .*)$', msg)
24 if user:
25 return user.group(1) + user.group(2)
26
27 logging.warning('Could not parse syslog message: ' + msg)
28 return msg
29
30
Mark Koudritsky1752f9f2012-05-16 17:12:14 -040031def extract_kernel_timestamp(msg):
32 """Extract a timestmap that appears in kernel log messages and looks
33 like this:
34 ... kernel: [78791.721832] ...
35
36 Returns:
37 The timestamp as float in seconds since last boot.
38 """
39
Julius Werner10feb5d2012-11-26 15:16:05 -080040 match = re.search('\[\s*([0-9]+\.[0-9]+)\] ', msg)
Mark Koudritsky1752f9f2012-05-16 17:12:14 -040041 if match:
42 return float(match.group(1))
43 raise error.TestError('Could not extract timestamp from message: ' + msg)
44
45
Eric Lie7c4cab2011-01-05 14:39:19 -080046class LogReader(object):
47 """
48 A class to read system log files.
49 """
50
Eric Li4844a9a2011-04-03 14:42:49 -070051 def __init__(self, filename='/var/log/messages', include_rotated_logs=True):
Eric Lie7c4cab2011-01-05 14:39:19 -080052 self._start_line = 1
53 self._filename = filename
Eric Li4844a9a2011-04-03 14:42:49 -070054 self._include_rotated_logs = include_rotated_logs
Eric Lie7c4cab2011-01-05 14:39:19 -080055 if not os.path.exists(CLEANUP_LOGS_PAUSED_FILE):
56 raise error.TestError('LogReader created without ' +
57 CLEANUP_LOGS_PAUSED_FILE)
58
59
Julius Werner10feb5d2012-11-26 15:16:05 -080060 def read_all_logs(self):
Eric Li4844a9a2011-04-03 14:42:49 -070061 """Read all content from log files.
62
63 Generator function.
64 Return an iterator on the content of files.
Julius Werner73ab5482013-01-15 11:00:07 -080065
66 This generator can peek a line once (and only once!) by using
67 .send(offset). Must iterate over the peeked line before you can
68 peek again.
Eric Li4844a9a2011-04-03 14:42:49 -070069 """
70 log_files = []
Julius Werner467cb2d2013-01-30 16:10:22 -080071 line_number = 0
Eric Li4844a9a2011-04-03 14:42:49 -070072 if self._include_rotated_logs:
73 log_files.extend(utils.system_output(
74 'ls -tr1 %s.*' % self._filename,
75 ignore_status=True).splitlines())
76 log_files.append(self._filename)
Eric Li4844a9a2011-04-03 14:42:49 -070077 for log_file in log_files:
78 f = open(log_file)
79 for line in f:
Julius Werner467cb2d2013-01-30 16:10:22 -080080 line_number += 1
81 if line_number < self._start_line:
82 continue
Julius Werner73ab5482013-01-15 11:00:07 -080083 peek = yield line
84 if peek:
85 buf = [f.next() for _ in xrange(peek)]
86 yield buf[-1]
87 while buf:
88 yield buf.pop(0)
Eric Li4844a9a2011-04-03 14:42:49 -070089 f.close()
90
91
Eric Lie7c4cab2011-01-05 14:39:19 -080092 def set_start_by_regexp(self, index, regexp):
93 """Set the start of logs based on a regular expression.
94
95 @param index: line matching regexp to start at, earliest log at 0.
96 Negative numbers indicate matches since end of log.
97 """
98 regexp_compiled = re.compile(regexp)
Eric Lie7c4cab2011-01-05 14:39:19 -080099 starts = []
100 line_number = 1
Julius Werner10feb5d2012-11-26 15:16:05 -0800101 self._start_line = 1
Eric Li4844a9a2011-04-03 14:42:49 -0700102 for line in self.read_all_logs():
Eric Lie7c4cab2011-01-05 14:39:19 -0800103 if regexp_compiled.match(line):
104 starts.append(line_number)
105 line_number += 1
106 if index < -len(starts):
107 self._start_line = 1
108 elif index >= len(starts):
Eric Li4844a9a2011-04-03 14:42:49 -0700109 self._start_line = line_number
Eric Lie7c4cab2011-01-05 14:39:19 -0800110 else:
111 self._start_line = starts[index]
112
113
114 def set_start_by_reboot(self, index):
115 """ Set the start of logs (must be system log) based on reboot.
116
117 @param index: reboot to start at, earliest log at 0. Negative
118 numbers indicate reboots since end of log.
119 """
120 return self.set_start_by_regexp(index,
121 r'.*000\] Linux version \d')
122
123
Eric Li4844a9a2011-04-03 14:42:49 -0700124 def set_start_by_current(self, relative=0):
Eric Lie7c4cab2011-01-05 14:39:19 -0800125 """ Set start of logs based on current last line.
126
127 @param relative: line relative to current to start at. 1 means
128 to start the log after this line.
129 """
Eric Li4844a9a2011-04-03 14:42:49 -0700130 count = self._start_line + relative
Julius Werner10feb5d2012-11-26 15:16:05 -0800131 for line in self.read_all_logs():
Eric Li4844a9a2011-04-03 14:42:49 -0700132 count += 1
133 self._start_line = count
Eric Lie7c4cab2011-01-05 14:39:19 -0800134
135
136 def get_logs(self):
137 """ Get logs since the start line.
138
139 Start line is set by set_start_* functions or
140 since the start of the file if none were called.
141
142 @return string of contents of file since start line.
143 """
Eric Li4844a9a2011-04-03 14:42:49 -0700144 logs = []
Julius Werner10feb5d2012-11-26 15:16:05 -0800145 for line in self.read_all_logs():
Eric Li4844a9a2011-04-03 14:42:49 -0700146 logs.append(line)
147 return ''.join(logs)
148
Eric Lie7c4cab2011-01-05 14:39:19 -0800149
150 def can_find(self, string):
151 """ Try to find string in the logs.
152
153 @return boolean indicating if we found the string.
154 """
Jason Glasgow68cf52f2011-03-31 15:32:13 -0400155 return string in self.get_logs()
Eric Lie7c4cab2011-01-05 14:39:19 -0800156
157
Julius Werner10feb5d2012-11-26 15:16:05 -0800158 def get_last_msg(self, patterns, retries=0, sleep_seconds=0.2):
Mark Koudritsky1752f9f2012-05-16 17:12:14 -0400159 """Search the logs and return the latest occurrence of a message
160 matching one of the patterns.
161
162 Args:
163 patterns: A regexp or a list of regexps to search the logs with.
164 The function returns as soon as it finds any match to one of
165 the patters, it will not search for the other patterns.
166 retries: Number of times to retry if none of the patterns were
167 found. Default is one attempt.
168 sleep_seconds: Time to sleep between retries.
169
170 Returns:
171 The last occurrence of the first matching pattern. "None" if none
172 of the patterns matched.
173 """
174
175 if not type(patterns) in (list, tuple):
176 patterns = [patterns]
177
178 for retry in xrange(retries + 1):
179 for pattern in patterns:
180 regexp_compiled = re.compile(pattern)
181 last_match = None
182 for line in self.read_all_logs():
183 if regexp_compiled.search(line):
184 last_match = line
185 if last_match:
186 return last_match
187 time.sleep(sleep_seconds)
188
189 return None
190
191
Eric Lie7c4cab2011-01-05 14:39:19 -0800192class LogRotationPauser(object):
193 """
194 Class to control when logs are rotated from either server or client.
195
196 Assumes all setting of CLEANUP_LOGS_PAUSED_FILE is done by this class
197 and that all calls to begin and end are properly
198 nested. For instance, [ a.begin(), b.begin(), b.end(), a.end() ] is
199 supported, but [ a.begin(), b.begin(), a.end(), b.end() ] is not.
200 We do support redundant calls to the same class, such as
201 [ a.begin(), a.begin(), a.end() ].
202 """
203 def __init__(self, host=None):
204 self._host = host
205 self._begun = False
206 self._is_nested = True
207
208
209 def _run(self, command, *args, **dargs):
210 if self._host:
211 return self._host.run(command, *args, **dargs).exit_status
212 else:
213 return utils.system(command, *args, **dargs)
214
215
216 def begin(self):
217 """Make sure that log rotation is disabled."""
218 if self._begun:
219 return
Eric Lie7c4cab2011-01-05 14:39:19 -0800220 self._is_nested = (self._run(('[ -r %s ]' %
221 CLEANUP_LOGS_PAUSED_FILE),
222 ignore_status=True) == 0)
Eric Lie7c4cab2011-01-05 14:39:19 -0800223 if self._is_nested:
Eric Lie7c4cab2011-01-05 14:39:19 -0800224 logging.info('File %s was already present' %
225 CLEANUP_LOGS_PAUSED_FILE)
Eric Lie7c4cab2011-01-05 14:39:19 -0800226 else:
227 self._run('touch ' + CLEANUP_LOGS_PAUSED_FILE)
Eric Lie7c4cab2011-01-05 14:39:19 -0800228 self._begun = True
229
230
231 def end(self):
Eric Lie7c4cab2011-01-05 14:39:19 -0800232 assert self._begun
233 if not self._is_nested:
234 self._run('rm -f ' + CLEANUP_LOGS_PAUSED_FILE)
235 else:
236 logging.info('Leaving existing %s file' % CLEANUP_LOGS_PAUSED_FILE)
237 self._begun = False