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  __version__ = "$Revision: 1.13 $"
     43  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     44  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     45
     46
     47  # stdlib
     48  import logging, sys, os, codecs, locale
     49
     50
     51  _logfile_name = None
     52  _logfile = None
     53
     54  _string_encoding = None
     55
     56  # table used for cooking non-printables
     57  AsciiName = ['<#0-0x00-nul>',
     58                           '<#1-0x01-soh>',
     59                           '<#2-0x02-stx>',
     60                           '<#3-0x03-etx>',
     61                           '<#4-0x04-eot>',
     62                           '<#5-0x05-enq>',
     63                           '<#6-0x06-ack>',
     64                           '<#7-0x07-bel>',
     65                           '<#8-0x08-bs>',
     66                           '<#9-0x09-ht>',
     67                           '<#10-0x0A-lf>',
     68                           '<#11-0x0B-vt>',
     69                           '<#12-0x0C-ff>',
     70                           '<#13-0x0D-cr>',
     71                           '<#14-0x0E-so>',
     72                           '<#15-0x0F-si>',
     73                           '<#16-0x10-dle>',
     74                           '<#17-0x11-dc1/xon>',
     75                           '<#18-0x12-dc2>',
     76                           '<#19-0x13-dc3/xoff>',
     77                           '<#20-0x14-dc4>',
     78                           '<#21-0x15-nak>',
     79                           '<#22-0x16-syn>',
     80                           '<#23-0x17-etb>',
     81                           '<#24-0x18-can>',
     82                           '<#25-0x19-em>',
     83                           '<#26-0x1A-sub>',
     84                           '<#27-0x1B-esc>',
     85                           '<#28-0x1C-fs>',
     86                           '<#29-0x1D-gs>',
     87                           '<#30-0x1E-rs>',
     88                           '<#31-0x1F-us>'
     89                          ]
     90
     91  # msg = reduce(lambda x, y: x+y, (map(self.__char2AsciiName, list(tmp))), '')
     92  #
     93  #       def __char2AsciiName(self, aChar):
     94  #               try:
     95  #                       return AsciiName[ord(aChar)]
     96  #               except IndexError:
     97  #                       return aChar
     98  #
     99  #       def __tracestack(self):
    100  #               """extract data from the current execution stack
    101  #
    102  #               this is rather fragile, I guess
    103  #               """
    104  #               stack = traceback.extract_stack()
    105  #               self.__modulename = stack[-4][0]
    106  #               self.__linenumber = stack[-4][1]
    107  #               self.__functionname = stack[-4][2]
    108  #               if (self.__functionname == "?"):
    109  #                       self.__functionname = "Main"
    110
    111  #===============================================================
    112  # external API
    113  #===============================================================
    114 -def flush():
    115          logger = logging.getLogger('gm.logging')
    116          logger.critical(u'-------- synced log file -------------------------------')
    117          root_logger = logging.getLogger()
    118          for handler in root_logger.handlers:
    119                  handler.flush()
    120  #===============================================================
    121 -def log_stack_trace(message=None):
    122
    123          logger = logging.getLogger('gm.logging')
    124
    125          tb = sys.exc_info()[2]
    126          if tb is None:
    127                  try:
    128                          tb = sys.last_traceback
    129                  except AttributeError:
    130                          logger.debug(u'no stack to trace')
    131                          return
    132
    133          # recurse back to root caller
    134          while 1:
    135                  if not tb.tb_next:
    136                          break
    137                  tb = tb.tb_next
    138          # and put the frames on a stack
    139          stack_of_frames = []
    140          frame = tb.tb_frame
    141          while frame:
    142                  stack_of_frames.append(frame)
    143                  frame = frame.f_back
    144          stack_of_frames.reverse()
    145
    146          if message is not None:
    147                  logger.debug(message)
    148          logger.debug(u'stack trace follows:')
    149          logger.debug(u'(locals by frame, outmost frame first)')
    150          for frame in stack_of_frames:
    151                  logger.debug (
    152                          u'>>> execution frame [%s] in [%s] at line %s <<<',
    153                          frame.f_code.co_name,
    154                          frame.f_code.co_filename,
    155                          frame.f_lineno
    156                  )
    157                  for varname, value in frame.f_locals.items():
    158                          if varname == u'__doc__':
    159                                  continue
    160
    161                          try:
    162                                  value = unicode(value, encoding = _string_encoding, errors = 'replace')
    163                          except TypeError:
    164                                  try:
    165                                          value = unicode(value)
    166                                  except (UnicodeDecodeError, TypeError):
    167                                          value = '%s' % str(value)
    168                                          value = value.decode(_string_encoding, 'replace')
    169
    170                          logger.debug(u'%20s = %s', varname, value)
    171  #===============================================================
    172 -def set_string_encoding(encoding=None):
    173
    174          logger = logging.getLogger('gm.logging')
    175
    176          global _string_encoding
    177
    178          if encoding is not None:
    179                  codecs.lookup(encoding)
    180                  _string_encoding = encoding
    181                  logger.info(u'setting python.str -> python.unicode encoding to <%s> (explicit)', _string_encoding)
    182                  return True
    183
    184          enc = sys.getdefaultencoding()
    185          if enc != 'ascii':
    186                  _string_encoding = enc
    187                  logger.info(u'setting python.str -> python.unicode encoding to <%s> (sys.getdefaultencoding)', _string_encoding)
    188                  return True
    189
    190          enc = locale.getlocale()[1]
    191          if enc is not None:
    192                  _string_encoding = enc
    193                  logger.info(u'setting python.str -> python.unicode encoding to <%s> (locale.getlocale)', _string_encoding)
    194                  return True
    195
    196          # FIXME: or rather use utf8 ?
    197          _string_encoding = locale.getpreferredencoding(do_setlocale=False)
    198          logger.info(u'setting python.str -> python.unicode encoding to <%s> (locale.getpreferredencoding)', _string_encoding)
    199          return True
    200  #===============================================================
    201  # internal API
    202  #===============================================================
    203 -def __setup_logging():
    204
    205          set_string_encoding()
    206
    207          global _logfile
    208          if _logfile is not None:
    209                  return True
    210
    211          if not __get_logfile_name():
    212                  return False
    213
    214          if sys.version[:3] < '2.5':
    215                  fmt = u'%(asctime)s  %(levelname)-8s  %(name)s (%(pathname)s @ #%(lineno)d): %(message)s'
    216          else:
    217                  fmt = u'%(asctime)s  %(levelname)-8s  %(name)s (%(pathname)s::%(funcName)s() #%(lineno)d): %(message)s'
    218
    219          _logfile = codecs.open(filename = _logfile_name, mode = 'wb', encoding = 'utf8', errors = 'replace')
    220
    221          logging.basicConfig (
    222                  format = fmt,
    223                  datefmt = '%Y-%m-%d %H:%M:%S',
    224                  level = logging.DEBUG,
    225                  stream = _logfile
    226          )
    227
    228          logger = logging.getLogger('gm.logging')
    229          logger.critical(u'-------- start of logging ------------------------------')
    230          logger.info(u'log file is <%s>', _logfile_name)
    231          logger.info(u'log level is [%s]', logging.getLevelName(logger.getEffectiveLevel()))
    232          logger.info(u'log file encoding is <utf8>')
    233          logger.info(u'initial python.str -> python.unicode encoding is <%s>', _string_encoding)
    234  #---------------------------------------------------------------
    235 -def __get_logfile_name():
    236
    237          global _logfile_name
    238          if _logfile_name is not None:
    239                  return _logfile_name
    240
    241          def_log_basename = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    242          def_log_name = '%s-%s.log' % (def_log_basename, os.getpid())
    243
    244          # given on command line ?
    245          for option in sys.argv[1:]:
    246                  if option.startswith('--log-file='):
    247                          (name,value) = option.split('=')
    248                          (dir, name) = os.path.split(value)
    249                          if dir == '':
    250                                  dir = '.'
    251                          if name == '':
    252                                  name = def_log_name
    253                          _logfile_name = os.path.abspath(os.path.expanduser(os.path.join(dir, name)))
    254                          return True
    255
    256          # else store it in ~/.def_log_basename/def_log_name
    257          dir = os.path.expanduser(os.path.join('~', '.' + def_log_basename))
    258          try:
    259                  os.makedirs(dir)
    260          except OSError, e:
    261                  if (e.errno == 17) and not os.path.isdir(dir):
    262                          raise
    263
    264          _logfile_name = os.path.join(dir, def_log_name)
    265
    266          return True
    267  #===============================================================
    268  # main
    269  #---------------------------------------------------------------
    270  __setup_logging()
    271
    272  if __name__ == '__main__':
    273
    274          #-----------------------------------------------------------
    275 -        def test():
    276                  logger = logging.getLogger('gmLog2.test')
    277                  logger.error("I expected to see %s::test()" % __file__)
    278                  try:
    279                          int(None)
    280                  except:
    281                          logger.exception(u'unhandled exception')
    282                          log_stack_trace()
    283                  flush()
    284          #-----------------------------------------------------------
    285          if len(sys.argv) > 1 and sys.argv[1] == u'test':
    286                  test()
    287  #===============================================================
    288  # $Log: gmLog2.py,v $
    289  # Revision 1.13  2009-01-02 11:37:44  ncq
    290  # - improved unicoding on log_stack_trace
    291  #
    292  # Revision 1.12  2008/06/11 19:11:48  ncq
    293  # - improved logging
    294  #
    295  # Revision 1.11  2008/05/31 17:44:31  ncq
    296  # - defautl format defs in u''
    297  #
    298  # Revision 1.10  2008/04/13 14:42:13  ncq
    299  # - saviour the last bits from the old logging code
    300  #
    301  # Revision 1.9  2008/03/06 21:23:22  ncq
    302  # - keep some interesting stuff from the old logging infrastructure
    303  #
    304  # Revision 1.8  2008/03/06 18:46:55  ncq
    305  # - fix docs
    306  #
    307  # Revision 1.7  2008/03/06 18:25:22  ncq
    308  # - fix docs
    309  #
    310  # Revision 1.6  2008/01/30 14:05:09  ncq
    311  # - a bit of cleanup
    312  # - TODO added
    313  #
    314  # Revision 1.5  2008/01/14 20:27:39  ncq
    315  # - set_string_encoding()
    316  # - properly encode values in log_stack_trace()
    317  # - proper test suite
    318  #
    319  # Revision 1.4  2008/01/13 01:15:41  ncq
    320  # - log_stack_trace() and test
    321  # - include PID in default log file name
    322  # - cleanup
    323  #
    324  # Revision 1.3  2008/01/07 19:48:53  ncq
    325  # - add flush()
    326  #
    327  # Revision 1.2  2007/12/12 16:23:21  ncq
    328  # - we want the default to be the default in GNUmed,
    329  #   no need to call it that
    330  #
    331  # Revision 1.1  2007/12/11 10:03:45  ncq
    332  # - eventually start switching to Python standard logging
    333  #
    334  #
    335