Coverage for cronlog.py: 21%
71 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-05-21 00:22 +0200
« prev ^ index » next coverage.py v7.2.2, created at 2023-05-21 00:22 +0200
1#
2# Copyright 2013, Martin Owens <doctormo@gmail.com>
3#
4# This library is free software; you can redistribute it and/or
5# modify it under the terms of the GNU Lesser General Public
6# License as published by the Free Software Foundation; either
7# version 3.0 of the License, or (at your option) any later version.
8#
9# This library is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12# Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public
15# License along with this library.
16#
17"""
18Access logs in known locations to find information about them.
19"""
21import os
22import re
23import codecs
24import platform
25from dateutil import parser as dateparse
27MATCHER = r'(?P<date>\w+ +\d+ +\d\d:\d\d:\d\d) (?P<host>\w+) ' + \
28 r'CRON\[(?P<pid>\d+)\]: \((?P<user>\w+)\) CMD \((?P<cmd>.*)\)'
30class LogReader(object):
31 """Opens a Log file, reading backwards and watching for changes"""
32 def __init__(self, filename, mass=4096):
33 self.filename = filename
34 self.mass = mass
35 self.size = -1
36 self.read = -1
37 self.pipe = None
39 def __enter__(self):
40 self.size = os.stat(self.filename)[6]
41 self.pipe = codecs.open(self.filename, 'r', encoding='utf-8')
42 return self
44 def __exit__(self, error_type, value, traceback):
45 self.pipe.close()
47 def __iter__(self):
48 if self.pipe is None:
49 with self as reader:
50 for (offset, line) in reader.readlines():
51 yield line
52 else:
53 for (offset, line) in self.readlines():
54 yield line
56 def readlines(self, until=0):
57 """Iterator for reading lines from a file backwards"""
58 if not self.pipe or self.pipe.closed:
59 raise IOError("Can't readline, no opened file.")
60 # Always seek to the end of the file, this accounts for file updates
61 # that happen during our running process.
62 location = self.size
63 halfline = ''
65 while location > until:
66 location -= self.mass
67 mass = self.mass
68 if location < 0:
69 mass = self.mass + location
70 location = 0
71 self.pipe.seek(location)
72 line = self.pipe.read(mass) + halfline
73 data = line.split('\n')
74 if location != 0:
75 halfline = data.pop(0)
76 loc = location + mass
77 data.reverse()
78 for line in data:
79 if line.strip() == '':
80 continue
81 yield (loc, line)
82 loc -= len(line)
86class CronLog(LogReader):
87 """Use the LogReader to make a Cron specific log reader"""
88 def __init__(self, filename='/var/log/syslog', user=None):
89 LogReader.__init__(self, filename)
90 self.user = user
92 def for_program(self, command):
93 """Return log entries for this specific command name"""
94 return ProgramLog(self, command)
96 def __iter__(self):
97 for line in super(CronLog, self).__iter__():
98 match = re.match(MATCHER, str(line))
99 datum = match and match.groupdict()
100 if datum and (not self.user or datum['user'] == self.user):
101 datum['date'] = dateparse.parse(datum['date'])
102 yield datum
105class ProgramLog(object):
106 """Specific log control for a single command/program"""
107 def __init__(self, log, command):
108 self.log = log
109 self.command = command
111 def __iter__(self):
112 for entry in self.log:
113 if entry['cmd'] == str(self.command):
114 yield entry