Index

lognestmonster / 23adbb8

A general-purpose single-header C logging library and parser for event-based logs. (Incomplete)

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
9302 Sep 2019 18:2023adbb8update parser stuffJosh Stockin1126113N

Blob @ lognestmonster / parser / read.py

application/x-python6374 bytesdownload raw
1# lognestmonster Copyright (c) 2019 Joshua 'joshuas3' Stockin
2# <https://github.com/JoshuaS3/lognestmonster/>.
3
4
5# This file is part of lognestmonster.
6
7# lognestmonster is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11
12# lognestmonster is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16
17# You should have received a copy of the GNU General Public License
18# along with lognestmonster. If not, see <https://www.gnu.org/licenses/>.
19
20import struct
21import os
22
23STATEMENT_START = 0
24STATEMENT_END = 1
25EVENT_START = 2
26EVENT_END = 3
27
28VERBOSITY_LEVELS = {
29 0: "INIT",
30 1: "DEBUG",
31 2: "VERBOSE",
32 3: "VERYVERBOSE",
33 4: "WARNING",
34 5: "ERROR"
35}
36
37def ulonglong(bytestr):
38 return struct.unpack("@Q", bytestr)[0]
39def uchar(charv):
40 return struct.unpack("@B", charv)[0]
41def ushort(shortv):
42 return struct.unpack("@H", shortv)[0]
43
44
45class EventProto:
46 parent = None
47 pushed = [False]
48 def __init__(self):
49 self.parent = None
50 self.pushed = [False]
51
52class Reader:
53 fd = None
54
55 top_level = []
56
57 current_event = None
58
59 event_count = 0
60 statement_count = 0
61
62 seekable = True
63 file_size = 0
64 position = 0
65
66 version = 0
67 timestamp = 0
68
69 bad_bytes = 0
70
71 filter_time_start = -1
72 filter_time_end = -1
73 filter_verbosity = -1
74 filter_tag = -1
75
76 update_callbacks = []
77
78 def __init__(self, fd, seekable=True):
79 self.fd = fd
80
81 self.top_level = []
82
83 self.current_event = None
84
85 self.event_count = 0
86 self.statement_count = 0
87
88 self.seekable = seekable
89 self.file_size = 0
90 self.position = 0
91
92 self.version = 0
93 self.timestamp = 0
94
95 self.bad_bytes = 0
96
97 self.filter_time_start = -1
98 self.filter_time_end = -1
99 self.filter_verbosity = -1
100 self.filter_tag = -1
101
102 self.update_callbacks = []
103
104 def update(self):
105 for callback in self.update_callbacks:
106 callback()
107
108 def onupdate(self, callback):
109 self.update_callbacks.append(callback)
110
111 def size(self):
112 self.fd.seek(0, os.SEEK_END) # go to end of file and get position
113 newsize = self.fd.tell()
114 self.fd.seek(self.position) # return to previous position
115
116 is_diff = self.file_size is not newsize
117 self.file_size = newsize
118 return is_diff
119
120 def pos(self):
121 p = self.fd.tell()
122 self.position = p
123 return p
124
125 def seek(self, position):
126 self.position = position
127 self.fd.seek(self.position)
128
129 def read(self, byte_count):
130 return self.fd.read(byte_count)
131
132 def fetch_item(self, position):
133 previouspos = self.pos()
134 self.seek(position)
135 timestamp = ulonglong(self.read(8))
136 verbosity = uchar(self.read(1))
137 tag_size = uchar(self.read(1))
138 tag = self.read(tag_size).decode("utf-8")
139 message_size = ushort(self.read(2))
140 message = self.read(message_size).decode("utf-8")
141 self.seek(previouspos)
142 return (timestamp, verbosity, tag_size, tag, message_size, message)
143
144 def parse_block(self, in_byte):
145 if in_byte == STATEMENT_START: # the byte indicates a statement's start, begin interpreting
146 if self.seekable:
147 this_position = self.position + 1 # identify and save the seeker position of this statement
148
149 try:
150 timestamp = ulonglong(self.read(8))
151 verbosity = uchar(self.read(1))
152 tag_size = uchar(self.read(1))
153 tag = self.read(tag_size).decode("utf-8")
154
155 append = True
156
157 if self.filter_time_start is not -1 and append:
158 append = timestamp > self.filter_time_start
159
160 if self.filter_time_end is not -1 and append:
161 append = timestamp < self.filter_time_end
162
163 if self.filter_verbosity is not -1 and append:
164 append = verbosity in self.filter_verbosity
165
166 if self.filter_tag is not -1 and append:
167 append = tag == self.filter_tag
168
169 message_size = ushort(self.read(2))
170 if self.seekable:
171 self.read(message_size)
172 else:
173 message = self.read(message_size).decode("utf-8")
174
175 if self.seekable:
176 while uchar(self.read(1)) is not STATEMENT_END and self.pos() < self.file_size:
177 self.bad_bytes += 1
178 else:
179 while uchar(self.read(1)) is not STATEMENT_END:
180 self.bad_bytes += 1
181
182 if append == True:
183 self.statement_count += 1
184 self.update()
185 if self.current_event is not None:
186 if self.seekable:
187 self.current_event.pushed.append(this_position)
188 else:
189 self.current_event.pushed.append((timestamp, verbosity, tag_size, tag, message_size, message))
190 else:
191 if self.seekable:
192 self.top_level.append(this_position)
193 else:
194 self.top_level.append((timestamp, verbosity, tag_size, tag, message_size, message))
195 except:
196 return -1
197
198 elif in_byte == EVENT_START: # the byte indicates an event's start, create an event
199 new_event = EventProto()
200 if self.current_event is not None: # we're already inside an event, set the new event's parent to match
201 new_event.parent = self.current_event
202 self.current_event = new_event
203
204
205 elif in_byte == EVENT_END: # end of event
206 if self.current_event is not None:
207 if len(self.current_event.pushed) > 1:
208 self.event_count += 1
209 self.update()
210 if self.current_event.parent is not None:
211 self.current_event.parent.pushed.append(self.current_event)
212 self.current_event = self.current_event.parent
213 else:
214 self.top_level.append(self.current_event)
215 self.current_event = None
216 else: # event is empty
217 if self.current_event.parent is not None:
218 self.current_event = self.current_event.parent
219 else:
220 self.current_event = None
221 else:
222 self.bad_bytes += 1 # event doesn't exist, bad byte
223 return -1
224
225 else: # unknown byte
226 self.bad_bytes += 1
227 return -1
228
229 def scan(self): # scan for events and statements from self.position to the end of file
230 if self.position == 0 and self.seekable: # if it's the start of the file, grab version and timestamp
231 self.version = uchar(self.read(1))
232 self.timestamp = ulonglong(self.read(8))
233
234 current_event = None
235
236 if self.pos() < self.file_size: # if the seeker is before EOF
237 while self.pos() < self.file_size: # while the seeker is before EOF
238 in_byte = uchar(self.read(1)) # read 1 byte
239 self.parse_block(in_byte) # parse block based on byte read
240
241