source: src/mlx/logger.py@ 554:b585c74956cb

Last change on this file since 554:b585c74956cb was 400:8b5838e0b51d, checked in by István Váradi <ivaradi@…>, 12 years ago

Using a lambda function instead of directly the property (re #172)

File size: 13.5 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 faultID = None, faultScore = None, clearFault = False):
100 """Create a copy of this entry with the given values changed."""
101 return Logger.Entry(None if clearTimestamp
102 else self._timestamp if timestamp is None
103 else timestamp,
104
105 self._text if text is None else text,
106
107 showTimestamp = self._showTimestamp,
108
109 faultID =
110 None if clearFault
111 else self._faultID if faultID is None
112 else faultID,
113
114 faultScore =
115 None if clearFault
116 else self._faultScore if faultScore is None
117 else faultScore,
118
119 id = self._id)
120
121 def __cmp__(self, other):
122 """Compare two entries
123
124 First their timestamps are compared, and if those are equal, then
125 their IDs."""
126 result = cmp(self._timestamp, other.timestamp)
127 if result==0:
128 result = cmp(self._id, other._id)
129 return result
130
131 class Fault(object):
132 """Information about a fault.
133
134 It contains the list of log entries that belong to this fault. The list
135 is ordered so that the first element contains the entry with the
136 highest score, so that it should be easy to find the actual score."""
137 def __init__(self, entry):
138 """Construct the fault info with the given log entry as its only
139 one."""
140 self._entries = [entry]
141
142 @property
143 def score(self):
144 """Get the score of this fault, i.e. the score of the entry with
145 the highest score."""
146 return self._entries[0].faultScore if self._entries else 0
147
148 def addEntry(self, entry):
149 """Add an entry to this fault.
150
151 The entries will be sorted."""
152 entries = self._entries
153 entries.append(entry)
154 entries.sort(key = lambda entry: entry.faultScore, reverse = True)
155
156 def removeEntry(self, entry):
157 """Remove the given entry.
158
159 Returns True if at least one entry remains, False otherwise."""
160 entries = self._entries
161 for index in range(0, len(entries)):
162 if entry is entries[index]:
163 del entries[index]
164 break
165
166 return len(entries)>0
167
168 def getLatestEntry(self):
169 """Get the entry with the highest score."""
170 return self._entries[0]
171
172 # FIXME: shall we use const.stage2string() instead?
173 _stages = { const.STAGE_BOARDING : "Boarding",
174 const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
175 const.STAGE_TAKEOFF : "Takeoff",
176 const.STAGE_RTO : "RTO",
177 const.STAGE_CLIMB : "Climb",
178 const.STAGE_CRUISE : "Cruise",
179 const.STAGE_DESCENT : "Descent",
180 const.STAGE_LANDING : "Landing",
181 const.STAGE_TAXIAFTERLAND : "Taxi",
182 const.STAGE_PARKING : "Parking",
183 const.STAGE_GOAROUND : "Go-Around",
184 const.STAGE_END : "End" }
185
186 NO_GO_SCORE = 10000
187
188 def __init__(self, output):
189 """Construct the logger."""
190 self._entries = {}
191 self._lines = []
192
193 self._faults = {}
194
195 self._output = output
196
197 @property
198 def lines(self):
199 """Get the lines of the log."""
200 return [(entry.timestampString, entry.text) for entry in self._lines]
201
202 @property
203 def faultLineIndexes(self):
204 """Get the sorted array of the indexes of those log lines that contain
205 a fault."""
206 faultLineIndexes = []
207 lines = self._lines
208 for index in range(0, len(lines)):
209 if lines[index].isFault:
210 faultLineIndexes.append(index)
211 return faultLineIndexes
212
213 def reset(self):
214 """Reset the logger.
215
216 The faults logged so far will be cleared."""
217 self._entries = {}
218 self._lines = []
219 self._faults = {}
220
221 def message(self, timestamp, msg):
222 """Put a simple textual message into the log with the given timestamp.
223
224 Returns an ID of the message so that it could be referred to later."""
225 return self._addEntry(Logger.Entry(timestamp, msg))
226
227 def untimedMessage(self, msg):
228 """Put an untimed message into the log."""
229 timestamp = self._lines[-1].timestamp if self._lines else 0
230 return self._addEntry(Logger.Entry(timestamp, msg,
231 showTimestamp = False))
232
233 def debug(self, msg):
234 """Log a debug message."""
235 print "[DEBUG]", msg
236
237 def stage(self, timestamp, stage):
238 """Report a change in the flight stage."""
239 s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
240 self.message(timestamp, "--- %s ---" % (s,))
241 if stage==const.STAGE_END:
242 self.untimedMessage("Rating: %.0f" % (self.getRating(),))
243 else:
244 messageType = \
245 const.MESSAGETYPE_INFLIGHT if stage in \
246 [const.STAGE_CLIMB, const.STAGE_CRUISE, \
247 const.STAGE_DESCENT, const.STAGE_LANDING] \
248 else const.MESSAGETYPE_INFORMATION
249 sendMessage(messageType, "Flight stage: " + s, 3)
250
251 def fault(self, faultID, timestamp, what, score,
252 updatePrevious = False, updateID = None):
253 """Report a fault.
254
255 faultID as a unique ID for the given kind of fault. If another fault of
256 this ID has been reported earlier, it will be reported again only if
257 the score is greater than last time. This ID can be, e.g. the checker
258 the report comes from.
259
260 If updatePrevious is True, and an instance of the given fault is
261 already in the log, only that instance will be updated with the new
262 timestamp and score. If there are several instances, the latest one
263 (with the highest score) will be updated. If updatePrevious is True,
264 and the new score is not greater than the latest one, the ID of the
265 latest one is returned.
266
267 If updateID is given, the log entry with the given ID will be
268 'upgraded' to be a fault with the given data.
269
270 Returns an ID of the fault, or -1 if it was not logged."""
271 fault = self._faults[faultID] if faultID in self._faults else None
272
273 if fault is not None and score<=fault.score:
274 return fault.getLatestEntry().id if updatePrevious else -1
275
276 text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
277 else "%s (%.1f)" % (what, score)
278
279 if updatePrevious and fault is not None:
280 latestEntry = fault.getLatestEntry()
281 id = latestEntry.id
282 newEntry = latestEntry.copy(timestamp = timestamp,
283 text = text,
284 faultScore = score)
285 self._updateEntry(id, newEntry)
286 elif updateID is not None:
287 id = updateID
288 newEntry = self._entries[id].copy(timestamp = timestamp,
289 text = text, faultID = faultID,
290 faultScore = score)
291 self._updateEntry(id, newEntry)
292 else:
293 id = self._addEntry(Logger.Entry(timestamp, text, faultID = faultID,
294 faultScore = score))
295
296 if updateID is None:
297 (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
298 if score==Logger.NO_GO_SCORE \
299 else (const.MESSAGETYPE_FAULT, 5)
300 sendMessage(messageType, text, duration)
301
302 return id
303
304 def noGo(self, faultID, timestamp, what):
305 """Report a No-Go fault."""
306 return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
307
308 def getRating(self):
309 """Get the rating of the flight so far."""
310 totalScore = 100
311 for fault in self._faults.itervalues():
312 score = fault.score
313 if score==Logger.NO_GO_SCORE:
314 return -score
315 else:
316 totalScore -= score
317 return totalScore
318
319 def updateLine(self, id, line):
320 """Update the line with the given ID with the given string.
321
322 Note, that it does not change the status of the line as a fault!"""
323 self._updateEntry(id, self._entries[id].copy(text = line))
324
325 def clearFault(self, id, text):
326 """Update the line with the given ID to contain the given string,
327 and clear its fault state."""
328 newEntry = self._entries[id].copy(text = text, clearFault = True)
329 self._updateEntry(id, newEntry)
330
331 def _addEntry(self, entry):
332 """Add the given entry to the log.
333
334 @return the ID of the new entry."""
335 assert entry.id not in self._entries
336
337 self._entries[entry.id] = entry
338
339 if not self._lines or entry>self._lines[-1]:
340 index = len(self._lines)
341 self._lines.append(entry)
342 else:
343 index = bisect.bisect_left(self._lines, entry)
344 self._lines.insert(index, entry)
345
346 if entry.isFault:
347 self._addFault(entry)
348
349 self._output.insertFlightLogLine(index, entry.timestampString,
350 entry.text, entry.isFault)
351
352 return entry.id
353
354 def _updateEntry(self, id, newEntry):
355 """Update the entry with the given ID from the given new entry."""
356 self._removeEntry(id)
357 self._addEntry(newEntry)
358
359 def _removeEntry(self, id):
360 """Remove the entry with the given ID."""
361 assert id in self._entries
362
363 entry = self._entries[id]
364 del self._entries[id]
365
366 for index in range(len(self._lines)-1, -1, -1):
367 if self._lines[index] is entry:
368 break
369 del self._lines[index]
370
371 if entry.isFault:
372 faultID = entry.faultID
373 fault = self._faults[faultID]
374 if not fault.removeEntry(entry):
375 del self._faults[faultID]
376
377 self._output.removeFlightLogLine(index)
378
379 def _addFault(self, entry):
380 """Add the given fault entry to the fault with the given ID."""
381 faultID = entry.faultID
382 if faultID in self._faults:
383 self._faults[faultID].addEntry(entry)
384 else:
385 self._faults[faultID] = Logger.Fault(entry)
386
387#--------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.