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 '--- 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 # internal API 214 #=============================================================== 215 __words2hide = [] 216
217 -def add_word2hide(word):
218 if word not in __words2hide: 219 __words2hide.append(word)
220 221 #--------------------------------------------------------------- 222 __original_logger_write_func = None 223
224 -def __safe_logger_write_func(s):
225 for word in __words2hide: 226 # throw away up to 4 bits (plus the randint() cost) 227 random.getrandbits(random.randint(1, 4)) 228 # from there generate a replacement string valid for 229 # *this* round of replacements of *this* word, 230 # this approach won't mitigate guessing trivial passwords 231 # from replacements of known data (a known-plaintext attack) 232 # but will make automated searching for replaced strings 233 # in the log more difficult 234 bummer = hex(random.randint(0, sys.maxsize)).lstrip('0x') 235 s = s.replace(word, bummer) 236 __original_logger_write_func(s)
237 238 #---------------------------------------------------------------
239 -def __setup_logging():
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 # setup 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 # start logging 267 #logger = logging.getLogger('gm.logging') 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 #---------------------------------------------------------------
275 -def __get_logfile_name():
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 # given on command line ? 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 # else store it in ~/.gnumed/logs/def_log_basename/default_logfile_name 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 # main 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 #-----------------------------------------------------------
326 - def test():
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