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 '--- frame [%s]: #%s, %s -------------------',
188 frame.f_code.co_name,
189 frame.f_lineno,
190 frame.f_code.co_filename
191 )
192 for varname, value in frame.f_locals.items():
193 if varname == '__doc__':
194 continue
195 logger.debug('%20s = %s', varname, value)
196
197
198 -def log_multiline(level, message=None, line_prefix=None, text=None):
199 if text is None:
200 return
201 if message is None:
202 message = 'multiline text:'
203 if line_prefix is None:
204 line_template = ' > %s'
205 else:
206 line_template = '%s: %%s' % line_prefix
207 lines2log = [message]
208 lines2log.extend([ line_template % line for line in text.split('\n') ])
209 logger = logging.getLogger('gm.logging')
210 logger.log(level, '\n'.join(lines2log))
211
212
213
214
215 __words2hide = []
216
220
221
222 __original_logger_write_func = None
223
225 for word in __words2hide:
226
227 random.getrandbits(random.randint(1, 4))
228
229
230
231
232
233
234 bummer = hex(random.randint(0, sys.maxsize)).lstrip('0x')
235 s = s.replace(word, bummer)
236 __original_logger_write_func(s)
237
238
240
241 global _logfile
242 if _logfile is not None:
243 return True
244
245 if not __get_logfile_name():
246 return False
247
248 _logfile = io.open(_logfile_name, mode = 'wt', encoding = 'utf8', errors = 'replace')
249 global __original_logger_write_func
250 __original_logger_write_func = _logfile.write
251 _logfile.write = __safe_logger_write_func
252
253
254 fmt = '%(asctime)s %(levelname)-8s %(name)-12s [%(thread)d %(threadName)-10s] (%(pathname)s::%(funcName)s() #%(lineno)d): %(message)s'
255 logging.basicConfig (
256 format = fmt,
257 datefmt = '%Y-%m-%d %H:%M:%S',
258 level = logging.DEBUG,
259 stream = _logfile
260 )
261 logging.captureWarnings(True)
262 logger = logging.getLogger()
263 logger.log_stack_trace = log_stack_trace
264 logger.log_multiline = log_multiline
265
266
267
268 logger.critical('-------- start of logging ------------------------------')
269 logger.info('log file is <%s>', _logfile_name)
270 logger.info('log level is [%s]', logging.getLevelName(logger.getEffectiveLevel()))
271 logger.info('log file encoding is <utf8>')
272 logger.debug('log file .write() patched from original %s to patched %s', __original_logger_write_func, __safe_logger_write_func)
273
274
276
277 global _logfile_name
278 if _logfile_name is not None:
279 return _logfile_name
280
281 def_log_basename = os.path.splitext(os.path.basename(sys.argv[0]))[0]
282 default_logfile_name = '%s-%s-%s.log' % (
283 def_log_basename,
284 pydt.datetime.now().strftime('%Y_%m_%d-%H_%M_%S'),
285 os.getpid()
286 )
287
288
289 for option in sys.argv[1:]:
290 if option.startswith('--log-file='):
291 (opt_name, value) = option.split('=')
292 (dir_name, file_name) = os.path.split(value)
293 if dir_name == '':
294 dir_name = '.'
295 if file_name == '':
296 file_name = default_logfile_name
297 _logfile_name = os.path.abspath(os.path.expanduser(os.path.join(dir_name, file_name)))
298 return True
299
300
301 dir_name = os.path.expanduser(os.path.join('~', '.gnumed', 'logs', def_log_basename))
302 try:
303 os.makedirs(dir_name)
304 except OSError as e:
305 if (e.errno == 17) and not os.path.isdir(dir_name):
306 raise
307
308 _logfile_name = os.path.join(dir_name, default_logfile_name)
309
310 return True
311
312
313
314
315 __setup_logging()
316
317 if __name__ == '__main__':
318
319 if len(sys.argv) < 2:
320 sys.exit()
321
322 if sys.argv[1] != 'test':
323 sys.exit()
324
325
327 logger = logging.getLogger('gmLog2.test')
328 logger.error("I expected to see %s::test()" % __file__)
329 add_word2hide('super secret passphrase')
330 logger.debug('credentials: super secret passphrase')
331
332 try:
333 int(None)
334 except:
335 logger.exception('unhandled exception')
336 log_stack_trace()
337 flush()
338
339 test()
340