Package Gnumed :: Package pycommon :: Module gmLog2
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmLog2

  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  # TODO: 
 49  # - exception() 
 50  # - ascii_ctrl2mnemonic() 
 51  #======================================================================== 
 52  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
 53  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
 54   
 55   
 56  # stdlib 
 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  #_string_encoding = None 
 73   
 74  # table used for cooking non-printables 
 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  # msg = reduce(lambda x, y: x+y, (map(self.__char2AsciiName, list(tmp))), '') 
110  # 
111  #       def __char2AsciiName(self, aChar): 
112  #               try: 
113  #                       return AsciiName[ord(aChar)] 
114  #               except IndexError: 
115  #                       return aChar 
116  # 
117  #       def __tracestack(self): 
118  #               """extract data from the current execution stack 
119  # 
120  #               this is rather fragile, I guess 
121  #               """ 
122  #               stack = traceback.extract_stack() 
123  #               self.__modulename = stack[-4][0] 
124  #               self.__linenumber = stack[-4][1] 
125  #               self.__functionname = stack[-4][2] 
126  #               if (self.__functionname == "?"): 
127  #                       self.__functionname = "Main" 
128   
129  #=============================================================== 
130  # external API 
131  #=============================================================== 
132 -def flush():
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 #===============================================================
140 -def log_instance_state(instance):
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 #===============================================================
147 -def log_stack_trace(message=None, t=None, v=None, tb=None):
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 # log exception details 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 # make sure we don't leave behind a binding 173 # to the traceback as warned against in 174 # sys.exc_info() documentation 175 try: 176 # recurse back to root caller 177 while 1: 178 if not tb.tb_next: 179 break 180 tb = tb.tb_next 181 # put the frames on a stack 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 # internal API 224 #=============================================================== 225 __words2hide = [] 226
227 -def add_word2hide(word):
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
238 -def __safe_logger_write_func(s):
239 for word in __words2hide: 240 # throw away up to 4 bits (plus the randint() cost) 241 random.getrandbits(random.randint(1, 4)) 242 # from there generate a replacement string valid for 243 # *this* round of replacements of *this* word, 244 # this approach won't mitigate guessing trivial passwords 245 # from replacements of known data (a known-plaintext attack) 246 # but will make automated searching for replaced strings 247 # in the log more difficult 248 bummer = hex(random.randint(0, sys.maxsize)).lstrip('0x') 249 s = s.replace(word, bummer) 250 __original_logger_write_func(s)
251 252 #---------------------------------------------------------------
253 -def __setup_logging():
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 # setup 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 # start logging 281 #logger = logging.getLogger('gm.logging') 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 #---------------------------------------------------------------
289 -def __get_logfile_name():
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 # given on command line ? 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 # else store it in ~/.gnumed/logs/def_log_basename/default_logfile_name 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 # main 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 #-----------------------------------------------------------
340 - def test():
341 logger = logging.getLogger('gmLog2.test') 342 logger.error("I expected to see %s::test()" % __file__) 343 add_word2hide('super secret passphrase') 344 logger.debug('credentials: super secret passphrase') 345 346 try: 347 int(None) 348 except Exception: 349 logger.exception('unhandled exception') 350 log_stack_trace() 351 flush()
352 #----------------------------------------------------------- 353 test() 354