1 """GNUmed logging framework setup.
2
3 All error logging, user notification and otherwise unhandled
4 exception handling should go through classes or functions of
5 this module.
6
7 Theory of operation:
8
9 This module tailors the standard logging framework to
10 the needs of GNUmed.
11
12 By importing gmLog2 into your code you'll get the root
13 logger send to a unicode file with messages in a format useful
14 for debugging. The filename is either taken from the
15 command line (--log-file=...) or derived from the name
16 of the main application.
17
18 The log file will be found in one of the following standard
19 locations:
20
21 1) given on the command line as "--log-file=LOGFILE"
22 2) ~/.<base_name>/<base_name>.log
23 3) /dir/of/binary/<base_name>.log (mainly for DOS/Windows)
24
25 where <base_name> is derived from the name
26 of the main application.
27
28 If you want to specify just a directory for the log file you
29 must end the --log-file definition with a slash.
30
31 By importing "logging" and getting a logger your modules
32 never need to worry about the real message destination or whether
33 at any given time there's a valid logger available.
34
35 Your MAIN module simply imports gmLog2 and all other modules
36 will merrily and automagically start logging away.
37 """
38
39
40
41
42 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
43 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
44
45
46
47 import logging
48 import sys
49 import os
50 import io
51 import codecs
52 import locale
53 import datetime as pydt
54 import random
55 import time
56 import calendar
57
58
59 _logfile_name = None
60 _logfile = None
61
62
63
64
65 AsciiName = ['<#0-0x00-nul>',
66 '<#1-0x01-soh>',
67 '<#2-0x02-stx>',
68 '<#3-0x03-etx>',
69 '<#4-0x04-eot>',
70 '<#5-0x05-enq>',
71 '<#6-0x06-ack>',
72 '<#7-0x07-bel>',
73 '<#8-0x08-bs>',
74 '<#9-0x09-ht>',
75 '<#10-0x0A-lf>',
76 '<#11-0x0B-vt>',
77 '<#12-0x0C-ff>',
78 '<#13-0x0D-cr>',
79 '<#14-0x0E-so>',
80 '<#15-0x0F-si>',
81 '<#16-0x10-dle>',
82 '<#17-0x11-dc1/xon>',
83 '<#18-0x12-dc2>',
84 '<#19-0x13-dc3/xoff>',
85 '<#20-0x14-dc4>',
86 '<#21-0x15-nak>',
87 '<#22-0x16-syn>',
88 '<#23-0x17-etb>',
89 '<#24-0x18-can>',
90 '<#25-0x19-em>',
91 '<#26-0x1A-sub>',
92 '<#27-0x1B-esc>',
93 '<#28-0x1C-fs>',
94 '<#29-0x1D-gs>',
95 '<#30-0x1E-rs>',
96 '<#31-0x1F-us>'
97 ]
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
123 logger = logging.getLogger('gm.logging')
124 logger.critical('-------- synced log file -------------------------------')
125 root_logger = logging.getLogger()
126 for handler in root_logger.handlers:
127 handler.flush()
128
129
131 logger = logging.getLogger('gm.logging')
132 logger.debug('state of %s', instance)
133 for attr in [ a for a in dir(instance) if not a.startswith('__') ]:
134 logger.debug(' %s: %s', attr, getattr(instance, attr))
135
136
138
139 logger = logging.getLogger('gm.logging')
140
141 if t is None:
142 t = sys.exc_info()[0]
143 if v is None:
144 v = sys.exc_info()[1]
145 if tb is None:
146 tb = sys.exc_info()[2]
147 if tb is None:
148 logger.debug('sys.exc_info() did not return a traceback object, trying sys.last_traceback')
149 try:
150 tb = sys.last_traceback
151 except AttributeError:
152 logger.debug('no stack to trace (no exception information available)')
153 return
154
155
156 logger.debug('exception: %s', v)
157 logger.debug('type: %s', t)
158 logger.debug('list of attributes:')
159 for attr in [ a for a in dir(v) if not a.startswith('__') ]:
160 logger.debug(' %s: %s', attr, getattr(v, attr))
161
162
163
164
165 try:
166
167 while 1:
168 if not tb.tb_next:
169 break
170 tb = tb.tb_next
171
172 stack_of_frames = []
173 frame = tb.tb_frame
174 while frame:
175 stack_of_frames.append(frame)
176 frame = frame.f_back
177 finally:
178 del tb
179 stack_of_frames.reverse()
180
181 if message is not None:
182 logger.debug(message)
183 logger.debug('stack trace follows:')
184 logger.debug('(locals by frame, outmost frame first)')
185 for frame in stack_of_frames:
186 logger.debug (
187 '>>> execution frame [%s] in [%s] at line %s <<<',
188 frame.f_code.co_name,
189 frame.f_code.co_filename,
190 frame.f_lineno
191 )
192 for varname, value in frame.f_locals.items():
193 if varname == '__doc__':
194 continue
195
196
197
198
199
200
201
202
203
204 logger.debug('%20s = %s', varname, value)
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 __words2hide = []
240
244
245
246 __original_logger_write_func = None
247
249 for word in __words2hide:
250
251 random.getrandbits(random.randint(1, 4))
252
253
254
255
256
257
258 bummer = hex(random.randint(0, sys.maxsize)).lstrip('0x')
259 s = s.replace(word, bummer)
260 __original_logger_write_func(s)
261
262
264
265
266
267 global _logfile
268 if _logfile is not None:
269 return True
270
271 if not __get_logfile_name():
272 return False
273
274 _logfile = io.open(_logfile_name, mode = 'wt', encoding = 'utf8', errors = 'replace')
275 global __original_logger_write_func
276 __original_logger_write_func = _logfile.write
277 _logfile.write = __safe_logger_write_func
278
279 fmt = '%(asctime)s %(levelname)-8s %(name)-12s [%(thread)d %(threadName)-10s] (%(pathname)s::%(funcName)s() #%(lineno)d): %(message)s'
280 logging.basicConfig (
281 format = fmt,
282 datefmt = '%Y-%m-%d %H:%M:%S',
283 level = logging.DEBUG,
284 stream = _logfile
285 )
286
287 logging.captureWarnings(True)
288
289 logger = logging.getLogger('gm.logging')
290 logger.critical('-------- start of logging ------------------------------')
291 logger.info('log file is <%s>', _logfile_name)
292 logger.info('log level is [%s]', logging.getLevelName(logger.getEffectiveLevel()))
293 logger.info('log file encoding is <utf8>')
294
295 logger.debug('log file .write() patched from original %s to patched %s', __original_logger_write_func, __safe_logger_write_func)
296
297
299
300 global _logfile_name
301 if _logfile_name is not None:
302 return _logfile_name
303
304 def_log_basename = os.path.splitext(os.path.basename(sys.argv[0]))[0]
305 default_logfile_name = '%s-%s-%s.log' % (
306 def_log_basename,
307 pydt.datetime.now().strftime('%Y_%m_%d-%H_%M_%S'),
308 os.getpid()
309 )
310
311
312 for option in sys.argv[1:]:
313 if option.startswith('--log-file='):
314 (opt_name, value) = option.split('=')
315 (dir_name, file_name) = os.path.split(value)
316 if dir_name == '':
317 dir_name = '.'
318 if file_name == '':
319 file_name = default_logfile_name
320 _logfile_name = os.path.abspath(os.path.expanduser(os.path.join(dir_name, file_name)))
321 return True
322
323
324 dir_name = os.path.expanduser(os.path.join('~', '.gnumed', 'logs', def_log_basename))
325 try:
326 os.makedirs(dir_name)
327 except OSError as e:
328 if (e.errno == 17) and not os.path.isdir(dir_name):
329 raise
330
331 _logfile_name = os.path.join(dir_name, default_logfile_name)
332
333 return True
334
335
336
337
338 __setup_logging()
339
340 if __name__ == '__main__':
341
342 if len(sys.argv) < 2:
343 sys.exit()
344
345 if sys.argv[1] != 'test':
346 sys.exit()
347
348
350 logger = logging.getLogger('gmLog2.test')
351 logger.error("I expected to see %s::test()" % __file__)
352 add_word2hide('super secret passphrase')
353 logger.debug('credentials: super secret passphrase')
354
355 try:
356 int(None)
357 except:
358 logger.exception('unhandled exception')
359 log_stack_trace()
360 flush()
361
362 test()
363