/ src / debug.py
debug.py
  1  """
  2  Logging and debuging facility
  3  -----------------------------
  4  
  5  Levels:
  6  
  7    DEBUG
  8      Detailed information, typically of interest only when diagnosing problems.
  9    INFO
 10      Confirmation that things are working as expected.
 11    WARNING
 12      An indication that something unexpected happened, or indicative of
 13      some problem in the near future (e.g. 'disk space low'). The software
 14      is still working as expected.
 15    ERROR
 16      Due to a more serious problem, the software has not been able to
 17      perform some function.
 18    CRITICAL
 19      A serious error, indicating that the program itself may be unable to
 20      continue running.
 21  
 22  There are three loggers by default: `console_only`, `file_only` and `both`.
 23  You can configure logging in the logging.dat in the appdata dir.
 24  It's format is described in the :func:`logging.config.fileConfig` doc.
 25  
 26  Use:
 27  
 28  >>> import logging
 29  >>> logger = logging.getLogger('default')
 30  
 31  The old form: ``from debug import logger`` is also may be used,
 32  but only in the top level modules.
 33  
 34  Logging is thread-safe so you don't have to worry about locks,
 35  just import and log.
 36  """
 37  
 38  import logging
 39  import logging.config
 40  import os
 41  import sys
 42  
 43  from six.moves import configparser
 44  
 45  import helper_startup
 46  import state
 47  
 48  helper_startup.loadConfig()
 49  
 50  # Now can be overriden from a config file, which uses standard python
 51  # logging.config.fileConfig interface
 52  # examples are here:
 53  # https://web.archive.org/web/20170712122006/https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163
 54  log_level = 'WARNING'
 55  
 56  
 57  def log_uncaught_exceptions(ex_cls, ex, tb):
 58      """The last resort logging function used for sys.excepthook"""
 59      logging.critical('Unhandled exception', exc_info=(ex_cls, ex, tb))
 60  
 61  
 62  def configureLogging():
 63      """
 64      Configure logging,
 65      using either logging.dat file in the state.appdata dir
 66      or dictionary with hardcoded settings.
 67      """
 68      sys.excepthook = log_uncaught_exceptions
 69      fail_msg = ''
 70      try:
 71          logging_config = os.path.join(state.appdata, 'logging.dat')
 72          logging.config.fileConfig(
 73              logging_config, disable_existing_loggers=False)
 74          return (
 75              False,
 76              'Loaded logger configuration from %s' % logging_config
 77          )
 78      except (OSError, configparser.NoSectionError, KeyError):
 79          if os.path.isfile(logging_config):
 80              fail_msg = \
 81                  'Failed to load logger configuration from %s, using default' \
 82                  ' logging config\n%s' % \
 83                  (logging_config, sys.exc_info())
 84          else:
 85              # no need to confuse the user if the logger config
 86              # is missing entirely
 87              fail_msg = 'Using default logger configuration'
 88  
 89      logging_config = {
 90          'version': 1,
 91          'formatters': {
 92              'default': {
 93                  'format': '%(asctime)s - %(levelname)s - %(message)s',
 94              },
 95          },
 96          'handlers': {
 97              'console': {
 98                  'class': 'logging.StreamHandler',
 99                  'formatter': 'default',
100                  'level': log_level,
101                  'stream': 'ext://sys.stderr'
102              },
103              'file': {
104                  'class': 'logging.handlers.RotatingFileHandler',
105                  'formatter': 'default',
106                  'level': log_level,
107                  'filename': os.path.join(state.appdata, 'debug.log'),
108                  'maxBytes': 2097152,  # 2 MiB
109                  'backupCount': 1,
110                  'encoding': 'UTF-8',
111              }
112          },
113          'loggers': {
114              'console_only': {
115                  'handlers': ['console'],
116                  'propagate': 0
117              },
118              'file_only': {
119                  'handlers': ['file'],
120                  'propagate': 0
121              },
122              'both': {
123                  'handlers': ['console', 'file'],
124                  'propagate': 0
125              },
126          },
127          'root': {
128              'level': log_level,
129              'handlers': ['console'],
130          },
131      }
132  
133      logging_config['loggers']['default'] = logging_config['loggers'][
134          'file_only' if '-c' in sys.argv else 'both']
135      logging.config.dictConfig(logging_config)
136  
137      return True, fail_msg
138  
139  
140  def resetLogging():
141      """Reconfigure logging in runtime when state.appdata dir changed"""
142      # pylint: disable=global-statement, used-before-assignment
143      global logger
144      for i in logger.handlers:
145          logger.removeHandler(i)
146          i.flush()
147          i.close()
148      configureLogging()
149      logger = logging.getLogger('default')
150  
151  
152  # !
153  
154  preconfigured, msg = configureLogging()
155  logger = logging.getLogger('default')
156  if msg:
157      logger.log(logging.WARNING if preconfigured else logging.INFO, msg)