1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
"""
Functions and classes to record and print out log messages.
This module provides a own logger class as well as specific functions to improve Pythons logging facility.
wxGlade uses the python logging instance with three log handler attached.
The first handler StringHandler is used to cache messages later displaying calling getBufferAsList() or
getBufferAsString().
The second handler logging.StreamHandler to print error messages to sys.stderr.
The third handler logging.FileHandler writes all messages into a file. This
behaviour is useful to store logged exceptions permanently.
@todo: Integrate Unicode logging fix.
@copyright: 2013-2016 Carsten Grohmann
@copyright: 2017-2021 Dietmar Schwertberger
@license: MIT (see LICENSE.txt) - THIS PROGRAM COMES WITH NO WARRANTY
"""
import datetime
import logging, logging.handlers
import os, sys, traceback
try:
_nameToLevel = logging._levelNames
except:
_nameToLevel = logging._nameToLevel
import config, compat
stringLoggerInstance = None # Reference to the active StringHandler instance
exception_orig = logging.exception # Reference to the original implementation of logging.exception
class StringHandler(logging.handlers.MemoryHandler):
"Stores the log records as a list of strings."
storeAsUnicode = True # Store the log records as unicode strings
# Encoding of all character strings
# The default encoding is used to convert character strings into unicode strings.
encoding = sys.stdout and sys.stdout.encoding or sys.getfilesystemencoding()
def __init__(self, storeAsUnicode=True):
"""Constructor
storeAsUnicode: Store recorded log records as unicode strings"""
self.buffer = [] # The message buffer itself
#logging.handlers.MemoryHandler.__init__(self, sys.maxint, 99)
logging.handlers.MemoryHandler.__init__(self, 2**31-1, 99)
self.storeAsUnicode = storeAsUnicode
def _toUnicode(self, msg):
"Convert a non unicode string into a unicode string"
# return msg if is already a unicode string or None
if msg is None or isinstance(msg, compat.unicode):
return msg
# convert character string into a unicode string
if not isinstance(msg, compat.unicode):
msg = msg.decode(self.encoding, 'replace')
return msg
def getBufferAsList(self, clean=True):
"Returns all buffered messages"
self.acquire()
try:
messages = self.buffer[:]
if clean:
self.flush()
finally:
self.release()
return messages
def getBufferAsString(self, clean=True):
"Returns all buffered messages"
msg_list = self.getBufferAsList(clean)
if self.storeAsUnicode:
return u'\n'.join(msg_list)
return '\n'.join(msg_list)
def emit(self, record):
"Emit a record, i.e. add a formatted log record to the buffer."
msg = self.format(record)
if self.storeAsUnicode:
msg = self._toUnicode(msg)
self.buffer.append(msg)
if self.shouldFlush(record):
self.flush()
def flush(self):
"Empty the buffer"
self.buffer = []
class ExceptionFormatter(logging.Formatter):
"Extended formatter to include more exception details automatically"
def formatException(self, ei):
"""Returns a detailed exception
ei: Tuple or list of exc_type, exc_value, exc_tb"""
exc_tb = ei[2]
exc_type = ei[0]
exc_value = ei[1]
msg = []
try:
try:
# log exception details
now = datetime.datetime.now().isoformat()
py_version = getattr(config, 'py_version', 'not found')
wx_version = getattr(config, 'wx_version', 'not found')
platform = getattr(config, 'platform', 'not found')
app_version = getattr(config, 'version', 'not found')
msg.append('An unexpected error occurred!\n')
msg.append('\n')
msg.append('Exception type: %s\n' % exc_type)
msg.append('Exception details: %s\n' % exc_value)
if exc_tb:
msg.append('\nApplication stack traceback:\n')
msg += traceback.format_tb(exc_tb)
msg.append('\n')
msg.append('Date and time: %s\n' % now)
msg.append('Python version: %s\n' % py_version)
msg.append('wxPython version: %s\n' % wx_version)
msg.append('wxWidgets platform: %s\n' % platform)
msg.append('wxGlade version: %s\n' % app_version)
msg.append('\n')
except Exception as e:
# This code should NEVER be executed!
if config.debugging: raise
logging.error('An exception has been raised inside the exception handler: %s', e)
sys.exit(1)
# delete local references of trace backs or part of them to avoid circular references
finally:
del ei, exc_tb, exc_type, exc_value
if msg[-1][-1] == "\n":
msg[-1] = msg[-1][:-1]
return "".join(msg)
def init(filename='wxglade.log', encoding='utf-8', level=None):
"""Initialise the logging facility
Initialise and configure the logging itself as well as the handlers described above.
Our own exception handler will be installed finally.
The file logger won't be instantiate if not file name is given.
filename: Name of the log file
encoding: Encoding of the log file
level: Verbosity of messages written in log file e.g. "INFO"
see: StringHandler, stringLoggerInstance, installExceptionHandler()"""
default_formatter = ExceptionFormatter('%(levelname)-8s: %(message)s')
file_formatter = ExceptionFormatter( '%(asctime)s %(name)s %(levelname)s: %(message)s' )
logger = logging.getLogger()
# check for installed handlers and remove them
for handler in logger.handlers[:]:
logger.removeHandler(handler)
# set newline sequence
if os.name == 'nt':
logging.StreamHandler.terminator = '\r\n'
elif os.name == 'mac':
logging.StreamHandler.terminator = '\r'
else:
logging.StreamHandler.terminator = '\n'
# instantiate console handler
console_logger = logging.StreamHandler()
if config.debugging:
console_logger.setLevel(logging.DEBUG)
else:
console_logger.setLevel(logging.INFO)
console_logger.setFormatter(default_formatter)
logger.addHandler(console_logger)
# instantiate file handler
if filename:
log_directory = os.path.dirname(filename)
if not os.path.isdir(log_directory):
logging.warning(_('Logging directory "%s" does not exists. Skip file logger initialisation!'),log_directory)
else:
file_logger = logging.handlers.RotatingFileHandler( filename, maxBytes=100 * 1024,
encoding=encoding, backupCount=1 )
file_logger.setFormatter(file_formatter)
file_logger.setLevel(logging.NOTSET)
logger.addHandler(file_logger)
# instantiate string handler
string_logger = StringHandler(storeAsUnicode=False)
string_logger.setLevel(logging.WARNING)
string_logger.setFormatter(default_formatter)
logger.addHandler(string_logger)
# store string logger globally
global stringLoggerInstance
stringLoggerInstance = string_logger
# don't filter log levels in root logger
logger.setLevel(logging.NOTSET)
# Set log level for root logger only
if level:
if level.upper() in _nameToLevel: # pylint: disable=W0212
logger.setLevel(_nameToLevel[level.upper()]) # pylint: disable=W0212
else:
logging.warning( _('Invalid log level "%s". Use "WARNING" instead.'), level.upper() )
logger.setLevel(logging.WARNING)
else:
logger.setLevel(logging.NOTSET)
# Install own exception handler at the end because it can raise a debug messages. This debug messages triggers the
# creation of a default StreamHandler if no log handler exists.
# There is a period of time with no log handler during this log initialisation.
if not config.debugging:
installExceptionHandler()
def deinit():
"Reactivate system exception handler; see deinstallExceptionHandler()"
deinstallExceptionHandler()
def setDebugLevel():
"Set the log level to DEBUG for all log handlers"
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def getBufferAsList(clean=True):
"""Returns all messages buffered by stringLoggerInstance as list of strings
clean: Clean the internal message buffer
see: StringHandler.getBufferAsList(), stringLoggerInstance"""
return stringLoggerInstance.getBufferAsList(clean)
def getBufferAsString(clean=True):
"""Returns all messages buffered by stringLoggerInstance as string
clean: Clean the internal message buffer
see StringHandler.getBufferAsString(), stringLoggerInstance"""
return stringLoggerInstance.getBufferAsString(clean)
def flush():
"""Empty the buffer of the stringLoggerInstance.
see: StringHandler.flush(), stringLoggerInstance"""
stringLoggerInstance.flush()
def installExceptionHandler():
"""Install own exception handler
see: deinstallExceptionHandler()"""
if sys.excepthook == exceptionHandler:
logging.debug( _('The exception handler has been installed already.') )
return
sys.excepthook = exceptionHandler
def deinstallExceptionHandler():
"""Restore the original exception handler from sys.__excepthook__.
see: installExceptionHandler()"""
sys.excepthook = sys.__excepthook__
def exceptionHandler(exc_type, exc_value, exc_tb):
"""Log detailed information about uncaught exceptions. The exception information will be cleared after that.
exc_type: Type of the exception (normally a class object)
exc_value: The "value" of the exception
exc_tb: Call stack of the exception"""
logging.error( _("An unhandled exception occurred"), exc_info=(exc_type, exc_value, exc_tb) )
if compat.PYTHON2: sys.exc_clear()
def getMessage(self):
"""Return the message for this LogRecord.
Return the message for this LogRecord after merging any user-supplied arguments with the message.
This specific version tries to handle Unicode user-supplied arguments."""
msg = self.msg
if not isinstance(msg, compat.basestring):
try:
msg = compat.unicode(msg)
except UnicodeError:
msg = self.msg # Defer encoding till later
if self.args:
try:
msg = msg % self.args
except UnicodeError:
# TODO it's still an hack :-/
logging.error(_('Unknown format of arguments: %s'), self.args)
except TypeError:
# Errors caused by wrong message formatting
logging.exception(_('Wrong format of a log message'))
return msg
# inject own (improved) function
logging.LogRecord.getMessage = getMessage
|