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

LogPrinter._get_logger()   B

Complexity

Conditions 3

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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