source: src/mlx/logger.py@ 348:50659419e5d8

Last change on this file since 348:50659419e5d8 was 346:78e378a97bdf, checked in by István Váradi <ivaradi@…>, 12 years ago

It is now possible to log faults so that the last one is updated instead of writing a new one (#143)

File size: 12.6 KB
Line 
1
2from fs import sendMessage
3import const
4import util
5
6import sys
7import time
8import bisect
9
10#--------------------------------------------------------------------------------------
11
12## @package mlx.logger
13#
14# The module for the logger.
15#
16# While the program itself is "logger", it contains an internal logger, which
17# maintains the textual log containing information on the various events and is
18# the reason why the program is called "logger".
19#
20# The log is made up of lines containing an optional timestamp and the text of
21# the message. A line can be updated after having been put into the log by
22# referring to its index.
23#
24# The logger object also maintains a separate set of faults and ensures that
25# one fault type has only one score, even if that fault has been reported
26# multiple times.
27
28#--------------------------------------------------------------------------------------
29
30class Logger(object):
31 """The class with the interface to log the various events.
32
33 It contains a list of entries ordered by their timestamps and their ever
34 increasing IDs."""
35
36 class Entry(object):
37 """An entry in the log."""
38
39 # The ID of the next entry to be created
40 _nextID = 1
41
42 def __init__(self, timestamp, text, showTimestamp = True,
43 faultID = None, faultScore = 0, id = None):
44 """Construct the entry."""
45 if id is None:
46 self._id = self._nextID
47 Logger.Entry._nextID += 1
48 else:
49 self._id = id
50
51 self._timestamp = timestamp
52 self._text = text
53 self._showTimestamp = showTimestamp
54
55 self._faultID = faultID
56 self._faultScore = faultScore
57
58 @property
59 def id(self):
60 """Get the ID of the entry."""
61 return self._id
62
63 @property
64 def timestamp(self):
65 """Get the timestamp of this entry."""
66 return self._timestamp
67
68 @property
69 def timestampString(self):
70 """Get the timestamp string of this entry.
71
72 It returns None, if the timestamp of the entry is not visible."""
73 return util.getTimestampString(self._timestamp) \
74 if self._showTimestamp else None
75
76 @property
77 def text(self):
78 """Get the text of this entry."""
79 return self._text
80
81 @property
82 def isFault(self):
83 """Determine if this is a log entry about a fault."""
84 return self._faultID is not None
85
86 @property
87 def faultID(self):
88 """Get the fault ID of the entry.
89
90 It may be None, if the entry is not a fault entry."""
91 return self._faultID
92
93 @property
94 def faultScore(self):
95 """Get the fault score of the entry, if it is a fault."""
96 return self._faultScore
97
98 def copy(self, timestamp = None, clearTimestamp = False, text = None,
99 faultScore = None):
100 """Create a copy of this entry with the given values changed."""
101 assert faultScore is None or self._faultID is not None
102
103 return Logger.Entry(None if clearTimestamp
104 else self._timestamp if timestamp is None
105 else timestamp,
106
107 self._text if text is None else text,
108
109 showTimestamp = self._showTimestamp,
110
111 faultID = self._faultID,
112
113 faultScore =
114 self._faultScore if faultScore is None
115 else faultScore,
116
117 id = self._id)
118
119 def __cmp__(self, other):
120 """Compare two entries
121
122 First their timestamps are compared, and if those are equal, then
123 their IDs."""
124 result = cmp(self._timestamp, other.timestamp)
125 if result==0:
126 result = cmp(self._id, other._id)
127 return result
128
129 class Fault(object):
130 """Information about a fault.
131
132 It contains the list of log entries that belong to this fault. The list
133 is ordered so that the first element contains the entry with the
134 highest score, so that it should be easy to find the actual score."""
135 def __init__(self, entry):
136 """Construct the fault info with the given log entry as its only
137 one."""
138 self._entries = [entry]
139
140 @property
141 def score(self):
142 """Get the score of this fault, i.e. the score of the entry with
143 the highest score."""
144 return self._entries[0].faultScore if self._entries else 0
145
146 def addEntry(self, entry):
147 """Add an entry to this fault.
148
149 The entries will be sorted."""
150 entries = self._entries
151 entries.append(entry)
152 entries.sort(key = Logger.Entry.faultScore, reverse = True)
153
154 def removeEntry(self, entry):
155 """Remove the given entry.
156
157 Returns True if at least one entry remains, False otherwise."""
158 entries = self._entries
159 for index in range(0, len(entries)):
160 if entry is entries[index]:
161 del entries[index]
162 break
163
164 return len(entries)>0
165
166 def getLatestEntry(self):
167 """Get the entry with the highest score."""
168 return self._entries[0]
169
170 # FIXME: shall we use const.stage2string() instead?
171 _stages = { const.STAGE_BOARDING : "Boarding",
172 const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
173 const.STAGE_TAKEOFF : "Takeoff",
174 const.STAGE_RTO : "RTO",
175 const.STAGE_CLIMB : "Climb",
176 const.STAGE_CRUISE : "Cruise",
177 const.STAGE_DESCENT : "Descent",
178 const.STAGE_LANDING : "Landing",
179 const.STAGE_TAXIAFTERLAND : "Taxi",
180 const.STAGE_PARKING : "Parking",
181 const.STAGE_GOAROUND : "Go-Around",
182 const.STAGE_END : "End" }
183
184 NO_GO_SCORE = 10000
185
186 def __init__(self, output):
187 """Construct the logger."""
188 self._entries = {}
189 self._lines = []
190
191 self._faults = {}
192
193 self._output = output
194
195 @property
196 def lines(self):
197 """Get the lines of the log."""
198 return [(entry.timestampString, entry.text) for entry in self._lines]
199
200 @property
201 def faultLineIndexes(self):
202 """Get the sorted array of the indexes of those log lines that contain
203 a fault."""
204 faultLineIndexes = []
205 lines = self._lines
206 for index in range(0, len(lines)):
207 if lines[index].isFault:
208 faultLineIndexes.append(index)
209 return faultLineIndexes
210
211 def reset(self):
212 """Reset the logger.
213
214 The faults logged so far will be cleared."""
215 self._entries = {}
216 self._lines = []
217 self._faults = {}
218
219 def message(self, timestamp, msg):
220 """Put a simple textual message into the log with the given timestamp.
221
222 Returns an ID of the message so that it could be referred to later."""
223 return self._addEntry(Logger.Entry(timestamp, msg))
224
225 def untimedMessage(self, msg):
226 """Put an untimed message into the log."""
227 timestamp = self._lines[-1].timestamp if self._lines else 0
228 return self._addEntry(Logger.Entry(timestamp, msg,
229 showTimestamp = False))
230
231 def debug(self, msg):
232 """Log a debug message."""
233 print "[DEBUG]", msg
234
235 def stage(self, timestamp, stage):
236 """Report a change in the flight stage."""
237 s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
238 self.message(timestamp, "--- %s ---" % (s,))
239 if stage==const.STAGE_END:
240 self.untimedMessage("Rating: %.0f" % (self.getRating(),))
241 else:
242 messageType = \
243 const.MESSAGETYPE_INFLIGHT if stage in \
244 [const.STAGE_CLIMB, const.STAGE_CRUISE, \
245 const.STAGE_DESCENT, const.STAGE_LANDING] \
246 else const.MESSAGETYPE_INFORMATION
247 sendMessage(messageType, "Flight stage: " + s, 3)
248
249 def fault(self, faultID, timestamp, what, score,
250 updatePrevious = False):
251 """Report a fault.
252
253 faultID as a unique ID for the given kind of fault. If another fault of
254 this ID has been reported earlier, it will be reported again only if
255 the score is greater than last time. This ID can be, e.g. the checker
256 the report comes from.
257
258 If updatePrevious is True, and an instance of the given fault is
259 already in the log, only that instance will be updated with the new
260 timestamp and score. If there are several instances, the latest one
261 (with the highest score) will be updated. If updatePrevious is True,
262 and the new score is not greater than the latest one, the ID of the
263 latest one is returned.
264
265 Returns an ID of the fault, or -1 if it was not logged."""
266 fault = self._faults[faultID] if faultID in self._faults else None
267
268 if fault is not None and score<=fault.score:
269 return fault.getLatestEntry().id if updatePrevious else -1
270
271 text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
272 else "%s (%.1f)" % (what, score)
273
274 if updatePrevious and fault is not None:
275 latestEntry = fault.getLatestEntry()
276 id = latestEntry.id
277 newEntry = latestEntry.copy(timestamp = timestamp,
278 text = text,
279 faultScore = score)
280 self._updateEntry(id, newEntry)
281 else:
282 id = self._addEntry(Logger.Entry(timestamp, text, faultID = faultID,
283 faultScore = score))
284
285 (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
286 if score==Logger.NO_GO_SCORE \
287 else (const.MESSAGETYPE_FAULT, 5)
288 sendMessage(messageType, text, duration)
289
290 return id
291
292 def noGo(self, faultID, timestamp, what):
293 """Report a No-Go fault."""
294 return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
295
296 def getRating(self):
297 """Get the rating of the flight so far."""
298 totalScore = 100
299 for fault in self._faults.itervalues():
300 score = fault.score
301 if score==Logger.NO_GO_SCORE:
302 return -score
303 else:
304 totalScore -= score
305 return totalScore
306
307 def updateLine(self, id, line):
308 """Update the line with the given ID with the given string.
309
310 Note, that it does not change the status of the line as a fault!"""
311 self._updateEntry(id, self._entries[id].copy(text = line))
312
313 def _addEntry(self, entry):
314 """Add the given entry to the log.
315
316 @return the ID of the new entry."""
317 assert entry.id not in self._entries
318
319 self._entries[entry.id] = entry
320
321 if not self._lines or entry>self._lines[-1]:
322 index = len(self._lines)
323 self._lines.append(entry)
324 else:
325 index = bisect.bisect_left(self._lines, entry)
326 self._lines.insert(index, entry)
327
328 if entry.isFault:
329 self._addFault(entry)
330
331 self._output.insertFlightLogLine(index, entry.timestampString,
332 entry.text, entry.isFault)
333
334 return entry.id
335
336 def _updateEntry(self, id, newEntry):
337 """Update the entry with the given ID from the given new entry."""
338 self._removeEntry(id)
339 self._addEntry(newEntry)
340
341 def _removeEntry(self, id):
342 """Remove the entry with the given ID."""
343 assert id in self._entries
344
345 entry = self._entries[id]
346 del self._entries[id]
347
348 for index in range(len(self._lines)-1, -1, -1):
349 if self._lines[index] is entry:
350 break
351 del self._lines[index]
352
353 if entry.isFault:
354 faultID = entry.faultID
355 fault = self._faults[faultID]
356 if not fault.removeEntry(entry):
357 del self._faults[faultID]
358
359 self._output.removeFlightLogLine(index)
360
361 def _addFault(self, entry):
362 """Add the given fault entry to the fault with the given ID."""
363 faultID = entry.faultID
364 if faultID in self._faults:
365 self._faults[faultID].addEntry(entry)
366 else:
367 self._faults[faultID] = Logger.Fault(entry)
368
369#--------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.