10 class LogParseError (Exception) : |
10 class LogParseError (Exception) : |
11 """ |
11 """ |
12 Parsing some line failed |
12 Parsing some line failed |
13 """ |
13 """ |
14 |
14 |
15 def __init__ (self, offset, line, message) : |
15 def __init__ (self, line, offset, message) : |
16 super(LogParseError, self).__init__("%r@%s: %s" % (line, offset, message)) |
16 super(LogParseError, self).__init__("%r@%s: %s" % (line, offset, message)) |
17 |
17 |
18 class LogParser (object) : |
18 class LogParser (object) : |
19 """ |
19 """ |
20 Abstract interface |
20 Abstract interface |
54 _TARGET = r'(?P<target>.+?)' |
54 _TARGET = r'(?P<target>.+?)' |
55 _CHAN = r'(?P<channel>.+?)' |
55 _CHAN = r'(?P<channel>.+?)' |
56 _CHAN2 = r'(?P<channel2>.+?)' |
56 _CHAN2 = r'(?P<channel2>.+?)' |
57 _USERHOST = r'(?P<username>.*?)@(?P<hostname>.*?)' |
57 _USERHOST = r'(?P<username>.*?)@(?P<hostname>.*?)' |
58 _MSG = r'(?P<message>.*)' |
58 _MSG = r'(?P<message>.*)' |
|
59 _SRV1 = r'(?P<server1>.+?)' |
|
60 _SRV2 = r'(?P<server2>.+?)' |
59 |
61 |
60 # regular expressions for matching lines, by type |
62 # regular expressions for matching lines, by type |
61 TYPE_EXPRS = ( |
63 TYPE_EXPRS = ( |
62 ( LogTypes.LOG_OPEN, r'--- Log opened (?P<datetime>.+)' ), |
64 ( LogTypes.LOG_OPEN, r'--- Log opened (?P<datetime>.+)' ), |
63 ( LogTypes.LOG_CLOSE, r'--- Log closed (?P<datetime>.+)' ), |
65 ( LogTypes.LOG_CLOSE, r'--- Log closed (?P<datetime>.+)' ), |
71 ( LogTypes.NICK, _TS + r' -!- ' + _NICK + ' is now known as (?P<target>\S+)' ), |
73 ( LogTypes.NICK, _TS + r' -!- ' + _NICK + ' is now known as (?P<target>\S+)' ), |
72 ( LogTypes.QUIT, _TS + r' -!- ' + _NICK + ' \[' + _USERHOST + '\] has quit \[(?P<message>.*?)\]' ), |
74 ( LogTypes.QUIT, _TS + r' -!- ' + _NICK + ' \[' + _USERHOST + '\] has quit \[(?P<message>.*?)\]' ), |
73 ( LogTypes.TOPIC, _TS + r' -!- (' + _NICK + ' changed the topic of ' + _CHAN + ' to: (?P<topic>.*)|Topic unset by ' + _NICK2 + ' on ' + _CHAN2 + ')' ), |
75 ( LogTypes.TOPIC, _TS + r' -!- (' + _NICK + ' changed the topic of ' + _CHAN + ' to: (?P<topic>.*)|Topic unset by ' + _NICK2 + ' on ' + _CHAN2 + ')' ), |
74 |
76 |
75 ( LogTypes.SELF_NOTICE, _TS + r' \[notice\(' + _CHAN + '\)\] ' + _MSG ), |
77 ( LogTypes.SELF_NOTICE, _TS + r' \[notice\(' + _CHAN + '\)\] ' + _MSG ), |
76 ( LogTypes.SELF_NICK, _TS + r' -!- You\'re now known as (?P<target>\S+)' ), |
78 ( LogTypes.SELF_NICK, _TS + r' -!- You\'re now known as (?P<target>\S+)' ), |
|
79 |
|
80 ( LogTypes.NETSPLIT_START, _TS + r' -!- Netsplit ' + _SRV1 + ' <-> ' + _SRV2 + ' quits: (?P<nick_list>[^(]+)( \(\+(?P<count>\d+) more,\S+\))?'), |
|
81 ( LogTypes.NETSPLIT_END, _TS + r' -!- Netsplit over, joins: (?P<nick_list>[^(]+)( \(\+(?P<count>\d+) more\))?' ), |
|
82 |
|
83 ( 'DAY_CHANGED', r'--- Day changed (?P<date>.+)' ), |
77 ) |
84 ) |
78 |
85 |
79 # precompile |
86 # precompile |
80 TYPE_REGEXES = [(type, re.compile(expr)) for type, expr in TYPE_EXPRS] |
87 TYPE_REGEXES = [(type, re.compile(expr)) for type, expr in TYPE_EXPRS] |
81 |
88 |
120 |
127 |
121 # override date? |
128 # override date? |
122 if date : |
129 if date : |
123 dt = dt.replace(year=date.year, month=date.month, day=date.day) |
130 dt = dt.replace(year=date.year, month=date.month, day=date.day) |
124 |
131 |
|
132 elif 'date' in groups : |
|
133 # parse date-only datetime |
|
134 dt = datetime.datetime.strptime(groups['date'], '%a %b %d %Y') |
|
135 |
125 else : |
136 else : |
126 # no timestamp !? |
137 # no timestamp !? |
127 raise LogParseError(offset, line, "No timestamp") |
138 raise LogParseError(line, offset, "No timestamp") |
128 |
139 |
129 # now localize with timezone |
140 # now localize with timezone |
130 dtz = self.tz.localize(dt) |
141 dtz = self.tz.localize(dt) |
131 |
142 |
132 # channel, currently unused |
143 # channel, currently unused |
133 channel_name = (groups.get('channel') or groups.get('channel2')) |
144 channel_name = (groups.get('channel') or groups.get('channel2')) |
134 |
145 |
135 # source |
146 # source |
136 source = (groups.get('nickname') or groups.get('nickname2'), groups.get('username'), groups.get('hostname'), groups.get('flags')) |
147 if 'server1' in groups : |
|
148 source = (None, None, groups.get('server1'), None) |
|
149 |
|
150 else : |
|
151 source = (groups.get('nickname') or groups.get('nickname2'), groups.get('username'), groups.get('hostname'), groups.get('flags')) |
137 |
152 |
138 # target |
153 # target |
139 target = groups.get('target') |
154 if 'server2' in groups : |
|
155 target = groups.get('server2') |
|
156 |
|
157 else : |
|
158 target = groups.get('target') |
140 |
159 |
141 # data |
160 # data |
142 if 'message' in groups : |
161 if 'message' in groups : |
143 data = groups['message'] |
162 data = groups['message'] |
144 |
163 |
146 data = groups['mode'] |
165 data = groups['mode'] |
147 |
166 |
148 elif 'topic' in groups : |
167 elif 'topic' in groups : |
149 data = groups['topic'] |
168 data = groups['topic'] |
150 |
169 |
|
170 elif 'nick_list' in groups : |
|
171 # split into components |
|
172 list = groups['nick_list'].split(', ') |
|
173 |
|
174 # additional count? |
|
175 if 'count' in groups and groups['count'] : |
|
176 list.append('+%d' % int(groups['count'])) |
|
177 |
|
178 # join |
|
179 data = ' '.join(list) |
|
180 |
151 else : |
181 else : |
152 data = None |
182 data = None |
153 |
183 |
154 # build+return LogLine |
184 # custom types? |
155 return LogLine(channel, offset, type, dtz, source, target, data) |
185 if type == 'DAY_CHANGED' : |
|
186 # new date |
|
187 date = dtz |
|
188 |
|
189 # build+return (date, LogLine) |
|
190 return date, LogLine(channel, offset, type, dtz, source, target, data) |
156 |
191 |
157 def parse_lines (self, channel, lines, date=None, starting_offset=None) : |
192 def parse_lines (self, channel, lines, date=None, starting_offset=None) : |
158 """ |
193 """ |
159 Parse the given lines, yielding LogEvents. |
194 Parse the given lines, yielding LogEvents. |
160 """ |
195 """ |