Coverage for cronlog.py: 21%

71 statements  

« 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""" 

20 

21import os 

22import re 

23import codecs 

24import platform 

25from dateutil import parser as dateparse 

26 

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>.*)\)' 

29 

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 

38 

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 

43 

44 def __exit__(self, error_type, value, traceback): 

45 self.pipe.close() 

46 

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 

55 

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 = '' 

64 

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) 

83 

84 

85 

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 

91 

92 def for_program(self, command): 

93 """Return log entries for this specific command name""" 

94 return ProgramLog(self, command) 

95 

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 

103 

104 

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 

110 

111 def __iter__(self): 

112 for entry in self.log: 

113 if entry['cmd'] == str(self.command): 

114 yield entry 

115