Gnumed.pycommon.gmLog2

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