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)