1
2 from __future__ import with_statement
3
4 """This class handles error logging.
5 Duplicate messages are lumped together.
6 It will remember notes that will be printed
7 if there is a later error/warning message.
8 (The notes will be printed in chronological order.)
9
10 The idea is that you call L{note} to memorize information that you might want
11 to know if there is an error. Then if/when trouble happens, you call L{warn},
12 L{die}, L{catch}, or L{catchexit} and the accumulated notes will be printed.
13
14 Calling L{info} or L{dbg} just prints out interesting stuff for your log files.
15 If you set C{L{debug}=False}, then calls to L{dbg} will do nothing.
16
17 Thanks to U{http://farmdev.com/talks/unicode/} for explanations of unicode.
18 """
19
20 import os
21 import sys
22 import traceback
23 import threading
24 import g_ucode as U
25
26 debug = True
27 nocompress = False
28 Threads = False
29
30 _name = os.path.basename(sys.argv[0])
31
32
33
34
35
37 lock = threading.Lock()
38
40 self.reps = 0
41 self.last = None
42 self.sequence = 0
43 self.memory = {}
44 self.stderr = sys.stderr
45 self.stdout = sys.stdout
46 assert self.stderr is not None
47 assert self.stdout is not None
48
50 self.stdout.flush()
51 self.stderr.flush()
52 self.stdout = stdout
53 self.stderr = stderr
54
56 if Threads:
57 id = u'%s(%s)' % (msgtype, threading.currentThread().name)
58 else:
59 id = msgtype
60 t = (id, name, s)
61
62 if nocompress or t != self.last:
63 self.showdump(u'%s: %s: %s\n' % (id, name, s))
64 self.last = t
65 else:
66 self.clearmem()
67 self.reps += 1
68 assert self.stderr is not None
69 assert self.stdout is not None
70
72 if self.last is not None and self.reps>1:
73 self.stderr.write(U.e(u'%s: last message repeated %d times.\n'
74 % (self.last[0], self.reps)))
75 self.last = None
76 self.reps = 1
77
85
88
90 self.memory = {}
91 self.sequence = 0
92
94 tmp = [(sqn, k, val) for (k, (sqn,val)) in self.memory.items()]
95 tmp.sort()
96 for (sqn,k,val) in tmp:
97 self.stderr.write(U.e(u'#NOTE: %s = %s\n' % (k, val)))
98
99
100
101 self.clearmem()
102
104 self.memory[key] = (self.sequence, value)
105 self.sequence += 1
106
107 _q = counter()
108
110 """Output a fatal error message and terminate.
111 Before the error message, it summarizes all the calls to L{note}.
112 @param s: error message
113 @type s: str
114 """
115 s = U.u(s)
116 e = u'ERR: %s: %s' % (_name, s)
117 exit(1, e)
118
119
121 """Output a non-fatal warning.
122 Before the warning, it summarizes all the calls to L{note}.
123 @param s: warning message
124 @type s: str
125 """
126 global _q
127 with _q.lock:
128 _q.incoming(u'#WARN', _name, U.u(s))
129
130
132 """Output useful information.
133 Before the message, it summarizes all the calls to L{note}.
134 @param s: message
135 @type s: str
136 """
137 global _q
138 with _q.lock:
139 _q.incoming(u'#INFO', _name, U.u(s))
140
141
143 """Call this inside an except statement.
144 It will report the exception and any other information it has.
145 """
146 etype, value, tback = sys.exc_info()
147 if extext is None:
148 extext = u"die.catch: exception caught.\n"
149 with _q.lock:
150 _q.showdump(u'')
151 traceback.print_exception(etype, value, tback)
152 etype = None
153 value = None
154 tback = None
155 _q.stderr.flush()
156 _q.stdout.flush()
157
158
160 """Call this inside an except statement. It will report
161 all information and then exit."""
162 catch(extext)
163 exit(n, text=U.u(text))
164
165
167 """Output debugging information, if debug is nonzero.
168 Before the debugging info, it prints all information given to L{note}.
169 @param s: debug message
170 @type s: str
171 """
172 if debug:
173 global _q
174 _q.incoming(u'#DBG', _name, U.u(s))
175
176
177 -def exit(n, text=None):
178 """Exit, after dumping accumulated L{note}s.
179 @param n: the processes' exit code
180 @type n: int
181 @param text: something to print
182 @type text: L{str} or L{None}
183 """
184 global _q
185 text = U.u(text)
186 with _q.lock:
187 _q.showdump(u'')
188 if text is not None:
189 _q.stdout.write(U.e(u'%s\n' % text))
190 _q.stdout.flush()
191 _q.stderr.write(U.e(u'%s\n' % text))
192 _q.stderr.flush()
193 sys.exit(n)
194
195
197 """Flush out any accumulated messages. Should be called shortly before exit.
198 @note: This matters when C{nocompress=False} under conditions where
199 L{sys.stderr} or L{sys.stdout} may be closed before the program
200 terminates. Under those conditions, you might get a
201 "last message repeated %d times" message produced into a closed
202 file descriptor, which would lead to an L{IOError}.
203 """
204 _q.showreps()
205 _q.stdout.flush()
206 _q.stderr.flush()
207
208
209 -def note(key, value):
210 """Memorize a note, which will be output along with the next error/warning/info message.
211 These will be printed in the form '#NOTE key = value', with value converted to a string
212 at the time of printing. Note that only the most recent note is kept for each key.
213 @type key: str
214 @type value: anything
215 """
216 global _q
217 with _q.lock:
218 _q.memorize(key, U.u(value))
219
220
222 """Get the most recent value memorized by a call to L{note}.
223 @type key: str
224 @rtype: whatever was memorized
225 """
226 global _q
227 with _q.lock:
228 try:
229 return _q.memory[key][1]
230 except KeyError:
231 pass
232 return None
233
234
236 """Like a print statement, except it prints in utf-8 encoding instead of ASCII."""
237 print U.e(s)
238
240 """Like sys.stderr.write(), except it prints in utf-8 encoding instead of ASCII."""
241 return sys.stderr.write(U.e(s))
242
244 """Like sys.stdout.write(), except it prints in utf-8 encoding instead of ASCII."""
245 return sys.stdout.write(U.e(s))
246
247
248 if __name__ == '__main__':
249 debug = 1
250 info("You should see a debug message [\xc4\x87] next.")
251 dbg("This is the debug message.")
252 debug = 0
253 note("gleep", "oldest note")
254 note("foo", "bar")
255 note("foo", "fleep")
256 note("foo", "foo")
257 note("farf", "newest note")
258 info("You should not see a debug message next.")
259 dbg("This is the debug message you shouldn't see.")
260 warn("This is a warning.")
261 warn("This is a warning.")
262 warn("This is a warning.")
263 info("It should have been repeated three times.")
264 die("This is the end.")
265