forked from wxGlade/wxGlade
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog.py
318 lines (241 loc) · 11 KB
/
log.py
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-2020 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