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  # TODO: 
 39  # - exception() 
 40  # - ascii_ctrl2mnemonic() 
 41  #======================================================================== 
 42  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>" 
 43  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
 44   
 45   
 46  # stdlib 
 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  #_string_encoding = None 
 63   
 64  # table used for cooking non-printables 
 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  # msg = reduce(lambda x, y: x+y, (map(self.__char2AsciiName, list(tmp))), '') 
100  # 
101  #       def __char2AsciiName(self, aChar): 
102  #               try: 
103  #                       return AsciiName[ord(aChar)] 
104  #               except IndexError: 
105  #                       return aChar 
106  # 
107  #       def __tracestack(self): 
108  #               """extract data from the current execution stack 
109  # 
110  #               this is rather fragile, I guess 
111  #               """ 
112  #               stack = traceback.extract_stack() 
113  #               self.__modulename = stack[-4][0] 
114  #               self.__linenumber = stack[-4][1] 
115  #               self.__functionname = stack[-4][2] 
116  #               if (self.__functionname == "?"): 
117  #                       self.__functionname = "Main" 
118   
119  #=============================================================== 
120  # external API 
121  #=============================================================== 
122 -def flush():
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 #===============================================================
130 -def log_instance_state(instance):
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 #===============================================================
137 -def log_stack_trace(message=None, t=None, v=None, tb=None):
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 # log exception details 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 # make sure we don't leave behind a binding 163 # to the traceback as warned against in 164 # sys.exc_info() documentation 165 try: 166 # recurse back to root caller 167 while 1: 168 if not tb.tb_next: 169 break 170 tb = tb.tb_next 171 # put the frames on a stack 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 # value = '%s' % value 196 # try: 197 # value = str(value, encoding = _string_encoding, errors = 'replace') 198 # except TypeError: 199 # try: 200 # value = str(value) 201 # except (UnicodeDecodeError, TypeError): 202 # value = '%s' % str(value) 203 # value = value.decode(_string_encoding, 'replace') 204 logger.debug('%20s = %s', varname, value)
205 206 #=============================================================== 207 #def set_string_encoding(enc__oding=None): 208 # 209 # logger = logging.getLogger('gm.logging') 210 # 211 # global _string_encoding 212 # 213 # if encoding is not None: 214 # codecs.lookup(encoding) 215 # _string_encoding = encoding 216 # logger.info('setting python.str -> python.unicode encoding to <%s> (explicit)', _string_encoding) 217 # return True 218 # 219 # enc = sys.getdefaultencoding() 220 # if enc != 'ascii': 221 # _string_encoding = enc 222 # logger.info('setting python.str -> python.unicode encoding to <%s> (sys.getdefaultencoding)', _string_encoding) 223 # return True 224 # 225 # enc = locale.getlocale()[1] 226 # if enc is not None: 227 # _string_encoding = enc 228 # logger.info('setting python.str -> python.unicode encoding to <%s> (locale.getlocale)', _string_encoding) 229 # return True 230 # 231 # # FIXME: or rather use utf8 ? 232 # _string_encoding = locale.getpreferredencoding(do_setlocale=False) 233 # logger.info('setting python.str -> python.unicode encoding to <%s> (locale.getpreferredencoding)', _string_encoding) 234 # return True 235 236 #=============================================================== 237 # internal API 238 #=============================================================== 239 __words2hide = [] 240
241 -def add_word2hide(word):
242 if word not in __words2hide: 243 __words2hide.append(word)
244 245 #--------------------------------------------------------------- 246 __original_logger_write_func = None 247
248 -def __safe_logger_write_func(s):
249 for word in __words2hide: 250 # throw away up to 4 bits (plus the randint() cost) 251 random.getrandbits(random.randint(1, 4)) 252 # from there generate a replacement string valid for 253 # *this* round of replacements of *this* word, 254 # this approach won't mitigate guessing trivial passwords 255 # from replacements of known data (a known-plaintext attack) 256 # but will make automated searching for replaced strings 257 # in the log more difficult 258 bummer = hex(random.randint(0, sys.maxsize)).lstrip('0x') 259 s = s.replace(word, bummer) 260 __original_logger_write_func(s)
261 262 #---------------------------------------------------------------
263 -def __setup_logging():
264 265 # set_string_encoding() 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 # logger.info('initial python.str -> python.unicode encoding is <%s>', _string_encoding) 295 logger.debug('log file .write() patched from original %s to patched %s', __original_logger_write_func, __safe_logger_write_func)
296 297 #---------------------------------------------------------------
298 -def __get_logfile_name():
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 # given on command line ? 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 # else store it in ~/.gnumed/logs/def_log_basename/default_logfile_name 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 # main 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 #-----------------------------------------------------------
349 - def test():
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