blob: 0d703e3a91de87f161e0666d99f2d026cdfa563b [file] [log] [blame]
Austin Schuhd1b28992014-10-26 20:55:06 -07001#!/usr/bin/python3
Dave Smith6b2cb012016-03-06 13:10:31 -08002
Dave Smith6b2cb012016-03-06 13:10:31 -08003import re
Philipp Schrader7861dce2015-02-23 00:27:59 +00004
Dave Smith6b2cb012016-03-06 13:10:31 -08005"""
6A regular expression to match the envelope part of the log entry.
7Parsing of the JSON msg is handled elsewhere.
8"""
9LOG_RE = re.compile("""
10 (.*?) # 1 name
11 \((\d+)\) # 2 pid
12 \((\d+)\) # 3 message_index
13 :\s
14 (\w+?) # 4 level
15 \s+at\s+
16 (\d+\.\d+)s # 5 time
17 :\s
18 ([A-Za-z0-9_./-]+) # 6 filename
19 :\s
20 (\d+) # 7 linenumber
21 :\s
22 (.*) # 8 msg
23 """, re.VERBOSE)
24
Austin Schuhd1b28992014-10-26 20:55:06 -070025class LogEntry:
Dave Smithacbca192016-03-06 15:27:23 -080026 """
27 This class provides a way to parse log entries.
28 The header portion of the log entry is parsed eagerly.
29 The structured portion of a log entry is parsed on demand.
30 """
Austin Schuhd1b28992014-10-26 20:55:06 -070031
32 def __init__(self, line):
Dave Smith6b2cb012016-03-06 13:10:31 -080033 """Populates a LogEntry from a line."""
Dave Smith41e3b792016-03-07 17:45:50 -080034 self.line = line
Dave Smith6b2cb012016-03-06 13:10:31 -080035 m = LOG_RE.match(line)
36 if m is None:
37 print("LOG_RE failed on", line)
Dave Smith41e3b792016-03-07 17:45:50 -080038 return
Dave Smith6b2cb012016-03-06 13:10:31 -080039 self.name = m.group(1)
40 self.pid_index = int(m.group(2))
41 self.msg_index = int(m.group(3))
42 self.level = m.group(4)
43 self.time = float(m.group(5))
44 self.filename = m.group(6)
45 self.linenumber = m.group(7)
46 self.msg = m.group(8)
Dave Smith41e3b792016-03-07 17:45:50 -080047 self.struct_name = None
Austin Schuhd1b28992014-10-26 20:55:06 -070048
49 def __str__(self):
50 """Formats the data cleanly."""
Dave Smithacbca192016-03-06 15:27:23 -080051 return '%s(%d)(%d): %s at %fs: %s: %d: %s' % (
52 self.name, self.pid, self.msg_index, self.level, self.time, self.filename, self.linenumber, self.msg)
Austin Schuhd1b28992014-10-26 20:55:06 -070053
Dave Smithacbca192016-03-06 15:27:23 -080054 def ParseStruct(self):
55 """Parses the message as a structure.
Austin Schuhd1b28992014-10-26 20:55:06 -070056
57 Returns:
Dave Smithacbca192016-03-06 15:27:23 -080058 struct_name, struct_type, json dict.
Austin Schuhd1b28992014-10-26 20:55:06 -070059 """
Dave Smith41e3b792016-03-07 17:45:50 -080060 if self.struct_name:
61 # We've already parsed the structural part. Return the cached result
62 return (self.struct_name, self.struct_type, self.struct_json)
63
Dave Smithacbca192016-03-06 15:27:23 -080064 struct_name_index = self.msg.find(':')
65 struct_name = self.msg[0:struct_name_index]
Austin Schuhd1b28992014-10-26 20:55:06 -070066
Dave Smithacbca192016-03-06 15:27:23 -080067 struct_body = self.msg[struct_name_index+2:]
68 tokens = []
69 this_token = ''
70 # For the various deliminators, append what we have found so far to the
71 # list and the token.
72 for char in struct_body:
73 if char == '{':
74 if this_token:
75 tokens.append(this_token)
76 this_token = ''
77 tokens.append('{')
78 elif char == '}':
79 if this_token:
80 tokens.append(this_token)
81 this_token = ''
82 tokens.append('}')
83 elif char == '[':
84 if this_token:
85 tokens.append(this_token)
86 this_token = ''
87 tokens.append('[')
88 elif char == ']':
89 if this_token:
90 tokens.append(this_token)
91 this_token = ''
92 tokens.append(']')
93 elif char == ':':
94 if this_token:
95 tokens.append(this_token)
96 this_token = ''
97 tokens.append(':')
98 elif char == ',':
99 if this_token:
100 tokens.append(this_token)
101 this_token = ''
102 tokens.append(',')
103 elif char == ' ':
104 if this_token:
105 tokens.append(this_token)
106 this_token = ''
Austin Schuhd1b28992014-10-26 20:55:06 -0700107 else:
Dave Smithacbca192016-03-06 15:27:23 -0800108 this_token += char
109 if this_token:
110 tokens.append(this_token)
Austin Schuhd1b28992014-10-26 20:55:06 -0700111
Dave Smithacbca192016-03-06 15:27:23 -0800112 struct_type = tokens[0]
113 json = dict()
114 # Now that we have tokens, parse them.
115 self.JsonizeTokens(json, tokens, 1)
116
Dave Smith41e3b792016-03-07 17:45:50 -0800117 # Cache the result to avoid having to reparse.
118 self.struct_name = struct_name
119 self.struct_type = struct_type
120 self.struct_json = json
121
Dave Smithacbca192016-03-06 15:27:23 -0800122 return (struct_name, struct_type, json)
Austin Schuhd1b28992014-10-26 20:55:06 -0700123
124 def JsonizeTokens(self, json, tokens, token_index):
125 """Creates a json-like dictionary from the provided tokens.
126
127 Args:
128 json: dict, The dict to stick the elements in.
129 tokens: list of strings, The list with all the tokens in it.
130 token_index: int, Where to start in the token list.
131
132 Returns:
133 int, The last token used.
134 """
135 # Check that the message starts with a {
136 if tokens[token_index] != '{':
137 print(tokens)
138 print('Expected { at beginning, found', tokens[token_index])
139 return None
140
141 # Eat the {
142 token_index += 1
143
144 # States and state variable for parsing elements.
145 STATE_INIT = 'init'
146 STATE_HAS_NAME = 'name'
147 STATE_HAS_COLON = 'colon'
148 STATE_EXPECTING_SUBMSG = 'submsg'
149 STATE_EXPECTING_COMMA = 'comma'
150 parser_state = STATE_INIT
151
152 while token_index < len(tokens):
153 if tokens[token_index] == '}':
154 # Finish if there is a }
155 return token_index + 1
156 elif tokens[token_index] == '{':
157 if parser_state != STATE_EXPECTING_SUBMSG:
158 print(tokens)
159 print(parser_state)
160 print('Bad input, was not expecting {')
161 return None
162 # Found a submessage, parse it.
163 sub_json = dict()
164 token_index = self.JsonizeTokens(sub_json, tokens, token_index)
165 json[token_name] = sub_json
166 parser_state = STATE_EXPECTING_COMMA
167 else:
168 if parser_state == STATE_INIT:
169 # This token is the name.
170 token_name = tokens[token_index]
171 parser_state = STATE_HAS_NAME
172 elif parser_state == STATE_HAS_NAME:
173 if tokens[token_index] != ':':
174 print(tokens)
175 print(parser_state)
176 print('Bad input, found', tokens[token_index], 'expected :')
177 return None
178 # After a name, comes a :
179 parser_state = STATE_HAS_COLON
180 elif parser_state == STATE_HAS_COLON:
181 # After the colon, figure out what is next.
182 if tokens[token_index] == '[':
183 # Found a sub-array!
184 sub_array = []
185 token_index = self.__JsonizeTokenArray(sub_array, tokens, token_index)
186 json[token_name] = sub_array
187 parser_state = STATE_EXPECTING_COMMA
188 elif tokens[token_index + 1] == '{':
189 # Found a sub-message, trigger parsing it.
190 parser_state = STATE_EXPECTING_SUBMSG
191 else:
192 # This is just an element, move on.
193 json[token_name] = tokens[token_index]
194 parser_state = STATE_EXPECTING_COMMA
195 elif parser_state == STATE_EXPECTING_COMMA:
196 # Complain if there isn't a comma here.
197 if tokens[token_index] != ',':
198 print(tokens)
199 print(parser_state)
200 print('Bad input, found', tokens[token_index], 'expected ,')
201 return None
202 parser_state = STATE_INIT
203 else:
204 print('Bad parser state')
205 return None
206 token_index += 1
207
208 print('Unexpected end')
209 return None
210
Dave Smithacbca192016-03-06 15:27:23 -0800211 def __JsonizeTokenArray(self, sub_array, tokens, token_index):
212 """Parses an array from the provided tokens.
213
214 Args:
215 sub_array: list, The list to stick the elements in.
216 tokens: list of strings, The list with all the tokens in it.
217 token_index: int, Where to start in the token list.
Austin Schuhd1b28992014-10-26 20:55:06 -0700218
219 Returns:
Dave Smithacbca192016-03-06 15:27:23 -0800220 int, The last token used.
Austin Schuhd1b28992014-10-26 20:55:06 -0700221 """
Dave Smithacbca192016-03-06 15:27:23 -0800222 # Make sure the data starts with a '['
223 if tokens[token_index] != '[':
224 print(tokens)
225 print('Expected [ at beginning, found', tokens[token_index + 1])
226 return None
Austin Schuhd1b28992014-10-26 20:55:06 -0700227
Dave Smithacbca192016-03-06 15:27:23 -0800228 # Eat the '['
229 token_index += 1
230
231 # Loop through the tokens.
232 while token_index < len(tokens):
233 if tokens[token_index + 1] == ',':
234 # Next item is a comma, so we should just add the element.
235 sub_array.append(tokens[token_index])
236 token_index += 2
237 elif tokens[token_index + 1] == ']':
238 # Next item is a ']', so we should just add the element and finish.
239 sub_array.append(tokens[token_index])
240 token_index += 1
241 return token_index
Austin Schuhd1b28992014-10-26 20:55:06 -0700242 else:
Dave Smithacbca192016-03-06 15:27:23 -0800243 # Otherwise, it must be a sub-message.
244 sub_json = dict()
245 token_index = self.JsonizeTokens(sub_json, tokens, token_index + 1)
246 sub_array.append(sub_json)
247 if tokens[token_index] == ',':
248 # Handle there either being another data element.
249 token_index += 1
250 elif tokens[token_index] == ']':
251 # Handle the end of the array.
252 return token_index
253 else:
254 print('Unexpected ', tokens[token_index])
255 return None
Austin Schuhd1b28992014-10-26 20:55:06 -0700256
Dave Smithacbca192016-03-06 15:27:23 -0800257 print('Unexpected end')
258 return None
Austin Schuhd1b28992014-10-26 20:55:06 -0700259
Austin Schuhd1b28992014-10-26 20:55:06 -0700260
261if __name__ == '__main__':
Dave Smithacbca192016-03-06 15:27:23 -0800262 def ParseLine(line):
263 return LogEntry(line)
264
Austin Schuhd1b28992014-10-26 20:55:06 -0700265 print('motor_writer(2240)(07421): DEBUG at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
266 line = ParseLine('motor_writer(2240)(07421): DEBUG at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
267 if '.aos.controls.OutputCheck' in line.msg:
268 print(line)
269 print(line.ParseStruct())
270
John Park33858a32018-09-28 23:05:48 -0700271 line = ParseLine('claw(2263)(19404): DEBUG at 0000000820.00000s: ../../aos/controls/control_loop-tmpl.h: 104: position: .frc971.control_loops.ClawGroup.Position{top:.frc971.control_loops.HalfClawPosition{position:1.672153, front:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:52}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:6, negedge_count:13}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:62}, posedge_value:0.642681, negedge_value:0.922207}, bottom:.frc971.control_loops.HalfClawPosition{position:1.353539, front:.frc971.HallEffectStruct{current:f, posedge_count:2, negedge_count:150}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:8, negedge_count:18}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:6}, posedge_value:0.434514, negedge_value:0.759491}}')
Austin Schuhd1b28992014-10-26 20:55:06 -0700272 print(line.ParseStruct())
273
274 line = ParseLine('joystick_proxy(2255)(39560): DEBUG at 0000000820.00730s: ../../aos/prime/input/joystick_input.cc: 61: sending: .aos.RobotState{joysticks:[.aos.Joystick{buttons:0, axis:[0.000000, 1.000000, 1.000000, 0.000000]}, .aos.Joystick{buttons:0, axis:[-0.401575, 1.000000, -1.007874, 0.000000]}, .aos.Joystick{buttons:0, axis:[0.007874, 0.000000, 1.000000, -1.007874]}, .aos.Joystick{buttons:0, axis:[0.000000, 0.000000, 0.000000, 0.000000]}], test_mode:f, fms_attached:f, enabled:T, autonomous:f, team_id:971, fake:f}')
275 print(line.ParseStruct())