File: log.py

package info (click to toggle)
wxglade 1%3A1.1.1%2Brepack-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 10,592 kB
  • sloc: python: 30,644; javascript: 740; makefile: 169; cpp: 99; perl: 90; lisp: 62; xml: 61; sh: 3
file content (318 lines) | stat: -rw-r--r-- 11,306 bytes parent folder | download
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