Completed
Pull Request — master (#2409)
by
unknown
02:05
created

LogPrinter._get_log_prefix()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
import sys
2
import traceback
3
import logging
4
from colorama import Style
5
6
from pyprint.Printer import Printer
7
from pyprint.ColorPrinter import ColorPrinter
8
9
from coalib.output.printers.LOG_LEVEL import LOG_LEVEL
10
from coalib.output.printers.LOG_LEVEL import LOG_LEVEL_COLORS
11
from coalib.processes.communication.LogMessage import LogMessage
12
13
14
class PrinterHandler(logging.StreamHandler):
15
    """
16
    Handler for logging which uses printer to print messages and falls back to
17
    writing to stdout if no printer is provided.
18
    """
19
    def __init__(self, printer=None, *args, **kwargs):
20
        """
21
        :param printer:  Printer object which will be used for printing
22
                         messages.
23
        """
24
        self.printer = printer
25
        super(PrinterHandler, self).__init__(*args, **kwargs)
26
27
    def emit(self, record):
28
        msg = self.format(record)
29
        if self.printer:
30
            self.printer.print(msg)
31
        else:
32
            sys.stdout.write(msg + "\n")
33
34
35
class ColoredFormatter(logging.Formatter):
36
37
    def __init__(self, *args, **kwargs):
38
        self.use_color = kwargs.pop('use_color', False)
39
        super(ColoredFormatter, self).__init__(*args, **kwargs)
40
41
    def format(self, record, *args, **kwargs):
42
        if record.levelno in LOG_LEVEL_COLORS and self.use_color:
43
            color_begin = LOG_LEVEL_COLORS[record.levelno]
44
            color_end = Style.RESET_ALL
45
        else:
46
            color_begin = color_end = ''
47
        record.levelname = '{cb}[{levelname}]{ce}'.format(
48
            cb=color_begin,
49
            ce=color_end,
50
            levelname=record.levelname)
51
        return super(ColoredFormatter, self).format(record, *args, **kwargs)
52
53
54
class LogPrinter:
55
    """
56
    The LogPrinter class allows to print log messages to an underlying Printer.
57
58
    This class is an adapter, means you can create a LogPrinter from every
59
    existing Printer instance.
60
    """
61
    _loggers = {}
62
63
    @staticmethod
64
    def _get_logger(printer, logger_name, time_fmt=None):
65
        """
66
        Returns existing or newly created and configured logger.
67
        :param logger_name: name of the logger to be returned.
68
        :param printer:     printer to initialize logger with if it doesn't
69
                            exist, yet.
70
        :param time_fmt:    format to be used for time output. No time is
71
                            pinter if None.
72
        """
73
        if not LogPrinter._loggers.get(logger_name):
74
            if time_fmt:
75
                format_str = '%(levelname)s[%(asctime)s] %(message)s'
76
            else:
77
                format_str = '%(levelname)s %(message)s'
78
79
            use_color = isinstance(printer, ColorPrinter)
80
            formatter = ColoredFormatter(format_str,
81
                                         time_fmt,
82
                                         use_color=use_color)
83
84
            handler = PrinterHandler(printer)
85
            handler.setFormatter(formatter)
86
87
            logger = logging.getLogger(logger_name)
88
            logger.addHandler(handler)
89
90
            LogPrinter._loggers[logger_name] = logger
91
        return LogPrinter._loggers[logger_name]
92
93
    def __init__(self,
94
                 printer=None,
95
                 log_level=LOG_LEVEL.INFO,
96
                 timestamp_format="%X",
97
                 of_type=True):
98
        """
99
        Creates a new log printer from an existing Printer or returns existing
100
        one if it already exist.
101
        Note: log_level changes on every logger instantiation.
102
103
        :param printer:          The underlying Printer where log messages
104
                                 shall be written to. If you inherit from
105
                                 Printer, set it to self.
106
        :param log_level:        The minimum log level, everything below will
107
                                 not be logged.
108
        :param timestamp_format: The format string for the
109
                                 datetime.today().strftime(format) method.
110
        :param of_type:          If False returns new logger object every time.
111
                                 Else may return logger which uses the same
112
                                 printer type or will use printer to initialize
113
                                 new one.
114
        """
115
        self.logger, self.logger_name = LogPrinter.getLogger(
116
            printer, of_type, timestamp_format)
117
118
        self._printer = printer
119
        self.log_level = log_level
120
        self.timestamp_format = timestamp_format
121
122
    @staticmethod
123
    def getLogger(printer=None, of_type=False, time_fmt=True):
124
        """
125
        Returns existing or new logger with appropriate printer.
126
127
        If printer is string: returns logger with name 'coala.<printer>'
128
        which may not be configured accordingly.
129
130
        If printer is Printer: if of_type is False returns logger with name
131
        'coala.<str(type(printer))>' which may use different printer, but of
132
        the same type, else returns logger with name 'coala.<str(printer)>
133
        which will use this exact printer for emitting logging messages.
134
135
        If printer is None: returns logger with name 'coala'.
136
137
        :param printer:    Printer instance or string. Ignored if logger
138
                           already exists.
139
        :param of_type:    if True returns logger with name
140
                           'coala.<str(type(printer)'. If it's not initialized
141
                           with printer, uses printer to initiate it.
142
        :param time_fmt:   Date format to use in logger. Ignored if logger
143
                           already exists.
144
        :return:           logger from logging module.
145
        """
146
        if printer is None:
147
            return logging.getLogger('coala'), 'coala'
148
        if isinstance(printer, str):
149
            return logging.getLogger(printer), printer
150
        if not isinstance(printer, Printer):
151
            raise TypeError(
152
                "printer should either be str or Printer."
153
                " Received {} instead.".format(type(printer)))
154
        if of_type:
155
            logger_name = "coala.{}".format(type(printer))
156
        else:
157
            logger_name = "coala.{}".format(printer)
158
        logger = LogPrinter._get_logger(printer, logger_name, time_fmt)
159
        return logger, logger_name
160
161
    @property
162
    def log_level(self):
163
        """
164
        Returns current log_level used is logger.
165
        """
166
        return self._log_level
167
168
    @log_level.setter
169
    def log_level(self, log_level):
170
        """
171
        Sets log_level for logger.
172
        """
173
        self._log_level = log_level
174
        self.logger.setLevel(log_level)
175
176
    @property
177
    def printer(self):
178
        """
179
        Returns the underlying printer where logs are printed to.
180
        """
181
        return self._printer
182
183
    def debug(self, *messages, delimiter=" ", timestamp=None, **kwargs):
184
        self.log_message(LogMessage(LOG_LEVEL.DEBUG,
185
                                    *messages,
186
                                    delimiter=delimiter,
187
                                    timestamp=timestamp),
188
                         **kwargs)
189
190
    def info(self, *messages, delimiter=" ", timestamp=None, **kwargs):
191
        self.log_message(LogMessage(LOG_LEVEL.INFO,
192
                                    *messages,
193
                                    delimiter=delimiter,
194
                                    timestamp=timestamp),
195
                         **kwargs)
196
197
    def warn(self, *messages, delimiter=" ", timestamp=None, **kwargs):
198
        self.log_message(LogMessage(LOG_LEVEL.WARNING,
199
                                    *messages,
200
                                    delimiter=delimiter,
201
                                    timestamp=timestamp),
202
                         **kwargs)
203
204
    def err(self, *messages, delimiter=" ", timestamp=None, **kwargs):
205
        self.log_message(LogMessage(LOG_LEVEL.ERROR,
206
                                    *messages,
207
                                    delimiter=delimiter,
208
                                    timestamp=timestamp),
209
                         **kwargs)
210
211
    def log(self, log_level, message, timestamp=None, **kwargs):
212
        self.log_message(LogMessage(log_level,
213
                                    message,
214
                                    timestamp=timestamp),
215
                         **kwargs)
216
217
    def log_exception(self,
218
                      message,
219
                      exception,
220
                      log_level=LOG_LEVEL.ERROR,
221
                      timestamp=None,
222
                      **kwargs):
223
        """
224
        If the log_level of the printer is greater than DEBUG, it prints
225
        only the message. If it is DEBUG or lower, it shows the message
226
        along with the traceback of the exception.
227
228
        :param message:   The message to print.
229
        :param exception: The exception to print.
230
        :param log_level: The log_level of this message (not used when
231
                          logging the traceback. Tracebacks always have
232
                          a level of DEBUG).
233
        :param timestamp: The time at which this log occured. Defaults to
234
                          the current time.
235
        :param kwargs:    Keyword arguments to be passed when logging the
236
                          message (not used when logging the traceback).
237
        """
238
        if not isinstance(exception, BaseException):
239
            raise TypeError("log_exception can only log derivatives of "
240
                            "BaseException.")
241
242
        traceback_str = "\n".join(
243
            traceback.format_exception(type(exception),
244
                                       exception,
245
                                       exception.__traceback__))
246
247
        self.log(log_level, message, timestamp=timestamp, **kwargs)
248
        self.log_message(
249
            LogMessage(LOG_LEVEL.DEBUG,
250
                       "Exception was:" + "\n" + traceback_str,
251
                       timestamp=timestamp),
252
            **kwargs)
253
254
    def log_message(self, log_message, **kwargs):
255
        if not isinstance(log_message, LogMessage):
256
            raise TypeError("log_message should be of type LogMessage.")
257
        self.logger.log(log_message.log_level, log_message.message)
258
259
    def __getstate__(self):
260
        # on Windows there are problems with serializing loggers, so omit it
261
        oldict = self.__dict__.copy()
262
        del oldict['logger']
263
        return oldict
264
265
    def __setstate__(self, newdict):
266
        self.__dict__.update(newdict)
267
        # restore logger by name
268
        self.logger = logging.getLogger(self.logger_name)
269