blob: fa19186abc25dc920652a9f0bc541db3a11b05bd [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
barfab@chromium.orgb6d29932012-04-11 09:46:43 +020010from 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 = []
Eric Li4844a9a2011-04-03 14:42:49 -070071 if self._include_rotated_logs:
72 log_files.extend(utils.system_output(
73 'ls -tr1 %s.*' % self._filename,
74 ignore_status=True).splitlines())
75 log_files.append(self._filename)
Eric Li4844a9a2011-04-03 14:42:49 -070076 for log_file in log_files:
77 f = open(log_file)
Julius Werner73ab5482013-01-15 11:00:07 -080078 for _ in xrange(self._start_line):
79 # read f up to the logical start of the log, discard the lines
80 f.next()
Eric Li4844a9a2011-04-03 14:42:49 -070081 for line in f:
Julius Werner73ab5482013-01-15 11:00:07 -080082 peek = yield line
83 if peek:
84 buf = [f.next() for _ in xrange(peek)]
85 yield buf[-1]
86 while buf:
87 yield buf.pop(0)
Eric Li4844a9a2011-04-03 14:42:49 -070088 f.close()
89
90
Eric Lie7c4cab2011-01-05 14:39:19 -080091 def set_start_by_regexp(self, index, regexp):
92 """Set the start of logs based on a regular expression.
93
94 @param index: line matching regexp to start at, earliest log at 0.
95 Negative numbers indicate matches since end of log.
96 """
97 regexp_compiled = re.compile(regexp)
Eric Lie7c4cab2011-01-05 14:39:19 -080098 starts = []
99 line_number = 1
Julius Werner10feb5d2012-11-26 15:16:05 -0800100 self._start_line = 1
Eric Li4844a9a2011-04-03 14:42:49 -0700101 for line in self.read_all_logs():
Eric Lie7c4cab2011-01-05 14:39:19 -0800102 if regexp_compiled.match(line):
103 starts.append(line_number)
104 line_number += 1
105 if index < -len(starts):
106 self._start_line = 1
107 elif index >= len(starts):
Eric Li4844a9a2011-04-03 14:42:49 -0700108 self._start_line = line_number
Eric Lie7c4cab2011-01-05 14:39:19 -0800109 else:
110 self._start_line = starts[index]
111
112
113 def set_start_by_reboot(self, index):
114 """ Set the start of logs (must be system log) based on reboot.
115
116 @param index: reboot to start at, earliest log at 0. Negative
117 numbers indicate reboots since end of log.
118 """
119 return self.set_start_by_regexp(index,
120 r'.*000\] Linux version \d')
121
122
Eric Li4844a9a2011-04-03 14:42:49 -0700123 def set_start_by_current(self, relative=0):
Eric Lie7c4cab2011-01-05 14:39:19 -0800124 """ Set start of logs based on current last line.
125
126 @param relative: line relative to current to start at. 1 means
127 to start the log after this line.
128 """
Eric Li4844a9a2011-04-03 14:42:49 -0700129 count = self._start_line + relative
Julius Werner10feb5d2012-11-26 15:16:05 -0800130 for line in self.read_all_logs():
Eric Li4844a9a2011-04-03 14:42:49 -0700131 count += 1
132 self._start_line = count
Eric Lie7c4cab2011-01-05 14:39:19 -0800133
134
135 def get_logs(self):
136 """ Get logs since the start line.
137
138 Start line is set by set_start_* functions or
139 since the start of the file if none were called.
140
141 @return string of contents of file since start line.
142 """
Eric Li4844a9a2011-04-03 14:42:49 -0700143 logs = []
Julius Werner10feb5d2012-11-26 15:16:05 -0800144 for line in self.read_all_logs():
Eric Li4844a9a2011-04-03 14:42:49 -0700145 logs.append(line)
146 return ''.join(logs)
147
Eric Lie7c4cab2011-01-05 14:39:19 -0800148
149 def can_find(self, string):
150 """ Try to find string in the logs.
151
152 @return boolean indicating if we found the string.
153 """
Jason Glasgow68cf52f2011-03-31 15:32:13 -0400154 return string in self.get_logs()
Eric Lie7c4cab2011-01-05 14:39:19 -0800155
156
Julius Werner10feb5d2012-11-26 15:16:05 -0800157 def get_last_msg(self, patterns, retries=0, sleep_seconds=0.2):
Mark Koudritsky1752f9f2012-05-16 17:12:14 -0400158 """Search the logs and return the latest occurrence of a message
159 matching one of the patterns.
160
161 Args:
162 patterns: A regexp or a list of regexps to search the logs with.
163 The function returns as soon as it finds any match to one of
164 the patters, it will not search for the other patterns.
165 retries: Number of times to retry if none of the patterns were
166 found. Default is one attempt.
167 sleep_seconds: Time to sleep between retries.
168
169 Returns:
170 The last occurrence of the first matching pattern. "None" if none
171 of the patterns matched.
172 """
173
174 if not type(patterns) in (list, tuple):
175 patterns = [patterns]
176
177 for retry in xrange(retries + 1):
178 for pattern in patterns:
179 regexp_compiled = re.compile(pattern)
180 last_match = None
181 for line in self.read_all_logs():
182 if regexp_compiled.search(line):
183 last_match = line
184 if last_match:
185 return last_match
186 time.sleep(sleep_seconds)
187
188 return None
189
190
Eric Lie7c4cab2011-01-05 14:39:19 -0800191class LogRotationPauser(object):
192 """
193 Class to control when logs are rotated from either server or client.
194
195 Assumes all setting of CLEANUP_LOGS_PAUSED_FILE is done by this class
196 and that all calls to begin and end are properly
197 nested. For instance, [ a.begin(), b.begin(), b.end(), a.end() ] is
198 supported, but [ a.begin(), b.begin(), a.end(), b.end() ] is not.
199 We do support redundant calls to the same class, such as
200 [ a.begin(), a.begin(), a.end() ].
201 """
202 def __init__(self, host=None):
203 self._host = host
204 self._begun = False
205 self._is_nested = True
206
207
208 def _run(self, command, *args, **dargs):
209 if self._host:
210 return self._host.run(command, *args, **dargs).exit_status
211 else:
212 return utils.system(command, *args, **dargs)
213
214
215 def begin(self):
216 """Make sure that log rotation is disabled."""
217 if self._begun:
218 return
219 print "in begin " + str(self._begun)
220 self._is_nested = (self._run(('[ -r %s ]' %
221 CLEANUP_LOGS_PAUSED_FILE),
222 ignore_status=True) == 0)
223 print "in begin is nested: " + str(self._is_nested)
224 if self._is_nested:
225 print logging.__file__
226 logging.info('File %s was already present' %
227 CLEANUP_LOGS_PAUSED_FILE)
228 print 1
229 else:
230 self._run('touch ' + CLEANUP_LOGS_PAUSED_FILE)
231 print 2
232 self._begun = True
233
234
235 def end(self):
236 print "in end" + str(self._begun)
237 assert self._begun
238 if not self._is_nested:
239 self._run('rm -f ' + CLEANUP_LOGS_PAUSED_FILE)
240 else:
241 logging.info('Leaving existing %s file' % CLEANUP_LOGS_PAUSED_FILE)
242 self._begun = False