22 """ |
22 """ |
23 Get logs for the given date (as a datetime) |
23 Get logs for the given date (as a datetime) |
24 """ |
24 """ |
25 |
25 |
26 abstract |
26 abstract |
27 |
27 |
28 class LogFile (LogSource) : |
28 def get_month_days (self, dt) : |
|
29 """ |
|
30 Get a set of dates, telling which days in the given month (as a datetime) have logs available |
|
31 """ |
|
32 |
|
33 abstract |
|
34 |
|
35 class LogFile (object) : |
29 """ |
36 """ |
30 A file containing LogEvents |
37 A file containing LogEvents |
|
38 |
|
39 XXX: modify to implement LogSource? |
31 """ |
40 """ |
32 |
41 |
33 def __init__ (self, path, parser, start_date=None, charset='utf-8', sep='\n') : |
42 def __init__ (self, path, parser, start_date=None, charset='utf-8', sep='\n') : |
34 """ |
43 """ |
35 Open the file at the given path, which contains data with the given charset, as lines separated by the |
44 Open the file at the given path, which contains data with the given charset, as lines separated by the |
166 # yield the rest a line at a time in reverse order... this looks weird, but that's how slicing works :) |
175 # yield the rest a line at a time in reverse order... this looks weird, but that's how slicing works :) |
167 # XXX: use something like islice, this has to build a slice object |
176 # XXX: use something like islice, this has to build a slice object |
168 for line in lines[:0:-1] : |
177 for line in lines[:0:-1] : |
169 yield line.decode(self.charset) |
178 yield line.decode(self.charset) |
170 |
179 |
171 def get_latest (self, count) : |
180 def read_latest (self, count) : |
172 """ |
181 """ |
173 Returns up to count events, from the end of the file, or less, if the file doesn't contain that many lines. |
182 Returns up to count events, from the end of the file, or less, if the file doesn't contain that many lines. |
174 """ |
183 """ |
175 |
184 |
176 # the list of lines |
185 # the list of lines |
219 dtz = dt.astimezone(self.tz) |
228 dtz = dt.astimezone(self.tz) |
220 |
229 |
221 # convert to date and use that |
230 # convert to date and use that |
222 return self._get_logfile_date(dtz.date()) |
231 return self._get_logfile_date(dtz.date()) |
223 |
232 |
224 def _get_logfile_date (self, d) : |
233 def _get_logfile_date (self, d, load=True) : |
225 """ |
234 """ |
226 Get the logfile corresponding to the given naive date in our timezone |
235 Get the logfile corresponding to the given naive date in our timezone. If load is False, only test for the |
|
236 presence of the logfile, do not actually open it. |
|
237 |
|
238 Returns None if the logfile does not exist. |
227 """ |
239 """ |
228 |
240 |
229 # format filename |
241 # format filename |
230 filename = d.strftime(self.filename_fmt) |
242 filename = d.strftime(self.filename_fmt) |
231 |
243 |
232 # build path |
244 # build path |
233 path = os.path.join(self.path, filename) |
245 path = os.path.join(self.path, filename) |
234 |
246 |
235 # return the LogFile |
247 try : |
236 return LogFile(path, self.parser, d, self.charset) |
248 if load : |
|
249 # open+return the LogFile |
|
250 return LogFile(path, self.parser, d, self.charset) |
|
251 |
|
252 else : |
|
253 # test |
|
254 return os.path.exists(path) |
|
255 |
|
256 # XXX: move to LogFile |
|
257 except IOError, e : |
|
258 # return None for missing files |
|
259 if e.errno == errno.ENOENT : |
|
260 return None |
|
261 |
|
262 else : |
|
263 raise |
237 |
264 |
238 def _iter_date_reverse (self, dt=None) : |
265 def _iter_date_reverse (self, dt=None) : |
239 """ |
266 """ |
240 Yields an infinite series of naive date objects in our timezone, iterating backwards in time starting at the |
267 Yields an infinite series of naive date objects in our timezone, iterating backwards in time starting at the |
241 given *datetime*, or the the current date, if none given |
268 given *datetime*, or the the current date, if none given |
278 |
305 |
279 # loop until done |
306 # loop until done |
280 while len(lines) < count : |
307 while len(lines) < count : |
281 logfile = None |
308 logfile = None |
282 |
309 |
283 try : |
310 # get next logfile |
284 # get next logfile |
311 files += 1 |
285 files += 1 |
312 |
286 |
313 # open |
287 # open |
314 logfile = self._get_logfile_date(day_iter.next()) |
288 logfile = self._get_logfile_date(day_iter.next()) |
315 |
289 |
316 if not logfile : |
290 except IOError, e : |
|
291 # skip nonexistant days if we haven't found any logs yet |
|
292 if e.errno != errno.ENOENT : |
|
293 raise |
|
294 |
|
295 if files > MAX_FILES : |
317 if files > MAX_FILES : |
296 raise Exception("No recent logfiles found") |
318 raise Exception("No recent logfiles found") |
297 |
319 |
298 else : |
320 else : |
299 # skip to next day |
321 # skip to next day |
300 continue |
322 continue |
301 |
323 |
302 # read the events |
324 # read the events |
303 # XXX: use a queue |
325 # XXX: use a queue |
304 lines = list(logfile.get_latest(count)) + lines |
326 lines = list(logfile.read_latest(count)) + lines |
305 |
327 |
306 # return the events |
328 # return the events |
307 return lines |
329 return lines |
308 |
330 |
309 def get_date (self, dt) : |
331 def get_date (self, dt) : |
331 f_end = self._get_logfile_date(d_end) |
353 f_end = self._get_logfile_date(d_end) |
332 |
354 |
333 # chain together the two sources |
355 # chain together the two sources |
334 return itertools.chain(f_begin.read_from(dtz_begin), f_end.read_until(dtz_end)) |
356 return itertools.chain(f_begin.read_from(dtz_begin), f_end.read_until(dtz_end)) |
335 |
357 |
|
358 def get_month_days (self, month) : |
|
359 """ |
|
360 Returns a set of dates for which logfiles are available in the given datetime's month |
|
361 """ |
|
362 |
|
363 # the set of days |
|
364 days = set() |
|
365 |
|
366 # iterate over month's days using Calendar |
|
367 for date in calendar.Calendar().itermonthdates(month.year, month.month) : |
|
368 # convert date to target datetime |
|
369 dtz = month.tzinfo.localize(datetime.datetime.combine(date, datetime.time(0))).astimezone(self.tz) |
|
370 |
|
371 # date in our target timezone |
|
372 log_date = dtz.date() |
|
373 |
|
374 # test for it |
|
375 if self._get_logfile_date(log_date, load=False) : |
|
376 # add to set |
|
377 days.add(date) |
|
378 |
|
379 # return set |
|
380 return days |
|
381 |