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