Edit on GitHub

common.lib.logger

Log handler

  1"""
  2Log handler
  3"""
  4import traceback
  5import platform
  6import logging
  7import time
  8import json
  9
 10from pathlib import Path
 11
 12from logging.handlers import RotatingFileHandler, HTTPHandler
 13
 14from common.config_manager import CoreConfigManager
 15
 16class WebHookLogHandler(HTTPHandler):
 17    """
 18    Basic HTTPHandler for webhooks via standard log handling
 19
 20    In essence, an HTTPHandler that formats its payload as JSON.
 21    """
 22    server_name = ""
 23
 24    def __init__(self, url):
 25        """
 26        Initialise WebHook handler
 27
 28        :param str url:  URL to send log messages to
 29        """
 30        host = url.split("/")[2]
 31        secure = url.lower().startswith("https")
 32
 33        super().__init__(host, url, method="POST", secure=secure)
 34
 35    def emit(self, record):
 36        """
 37        Emit a record
 38
 39        Send the record to the Web server as a percent-encoded dictionary
 40        This is the `emit()` method of the original HTTPHandler; the only
 41        change is that content is sent as JSON (which the webhooks expect)
 42        instead of urlencoded data.
 43
 44        :param logging.LogRecord record:  Log record to send
 45        """
 46        try:
 47            import http.client
 48            host = self.host
 49            if self.secure:
 50                h = http.client.HTTPSConnection(host, context=self.context)
 51            else:
 52                h = http.client.HTTPConnection(host)
 53            url = self.url
 54            ############### CHANGED FROM ORIGINAL ###############
 55            data = json.dumps(self.mapLogRecord(record))
 56            #####################################################
 57            if self.method == "GET":
 58                if (url.find('?') >= 0):
 59                    sep = '&'
 60                else:
 61                    sep = '?'
 62                url = url + "%c%s" % (sep, data)
 63            h.putrequest(self.method, url)
 64            # support multiple hosts on one IP address...
 65            # need to strip optional :port from host, if present
 66            i = host.find(":")
 67            if i >= 0:
 68                host = host[:i]
 69            # See issue #30904: putrequest call above already adds this header
 70            # on Python 3.x.
 71            # h.putheader("Host", host)
 72            if self.method == "POST":
 73                ############### CHANGED FROM ORIGINAL ###############
 74                h.putheader("Content-type", "application/json")
 75                #####################################################
 76                h.putheader("Content-length", str(len(data)))
 77            if self.credentials:
 78                import base64
 79                s = ('%s:%s' % self.credentials).encode('utf-8')
 80                s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
 81                h.putheader('Authorization', s)
 82            h.endheaders()
 83            if self.method == "POST":
 84                h.send(data.encode('utf-8'))
 85            h.getresponse()  # can't do anything with the result
 86        except Exception:
 87            self.handleError(record)
 88
 89
 90class SlackLogHandler(WebHookLogHandler):
 91    """
 92    Slack webhook log handler
 93    """
 94
 95    def mapLogRecord(self, record):
 96        """
 97        Format log message so it is compatible with Slack webhooks
 98
 99        :param logging.LogRecord record: Log record
100        """
101        if record.levelno in (logging.ERROR, logging.CRITICAL):
102            color = "#FF0000"  # red
103        elif record.levelno == logging.WARNING:
104            color = "#DD7711"  # orange
105        else:
106            color = "#3CC619"  # green
107
108        # simple stack trace
109        if record.stack:
110            # this is the stack where the log was called
111            frames = record.stack
112        else:
113            # the last 9 frames are not specific to the exception (general logging code etc)
114            # the frame before that is where the exception was raised
115            frames = traceback.extract_stack()[:-9]
116        location = "`%s`" % "` → `".join([frame.filename.split("/")[-1] + ":" + str(frame.lineno) for frame in frames])
117
118        # prepare slack webhook payload
119        fields = [{
120            "title": "Stack trace:",
121            "value": location,
122            "short": False
123        }]
124
125        # try to read some metadata from the offending file
126        try:
127            with Path(record.frame.filename).open() as infile:
128                fields.append({
129                    "title": "Code (`" + record.frame.filename.split("/")[-1] + ":" + str(record.frame.lineno) + "`):",
130                    "value": "```" + infile.readlines()[record.frame.lineno - 1].strip() + "```",
131                    "short": False
132                })
133        except (IndexError, AttributeError):
134            # the file is not readable, or the line number is out of bounds
135            pass
136
137        return {
138            "text": ":bell: 4CAT %s logged on `%s`:" % (record.levelname.lower(), platform.uname().node),
139            "mrkdwn_in": ["text"],
140            "attachments": [{
141                "color": color,
142                "text": record.message,
143                "fields": fields
144            }]
145        }
146
147
148class Logger:
149    """
150    Logger
151
152    Sets up a rotating logger that writes to a log file
153    """
154    logger = None
155    log_path = None
156    print_logs = True
157    db = None
158    previous_report = 0
159    levels = {
160        "DEBUG": logging.DEBUG,
161        "INFO": logging.INFO,
162        "WARNING": logging.WARNING,
163        "ERROR": logging.ERROR,
164        "CRITICAL": logging.CRITICAL,
165        "FATAL": logging.FATAL
166    }
167    alert_level = "FATAL"
168
169    def __init__(self, log_path="4cat.log", logger_name='4cat', output=False, log_level="INFO"):
170        """
171        Set up log handler
172
173        :param str|Path filename:  File path that will be written to
174        :param str logger_name:  Identifier for logging context
175        :param bool output:  Whether to print logs to output
176        :param str log_level:  Messages at this level or below will be logged
177        """
178        if self.logger:
179            return
180        log_level = self.levels.get(log_level, logging.INFO)
181
182        self.print_logs = output
183
184        if type(log_path) is str:
185            core_config = CoreConfigManager()
186            log_path = core_config.get("PATH_LOGS").joinpath(log_path)
187
188        if not log_path.parent.exists():
189            log_path.parent.mkdir(parents=True)
190
191        self.log_path = log_path
192        self.previous_report = time.time()
193
194        self.logger = logging.getLogger(logger_name)
195        self.logger.setLevel(log_level)
196
197        # this handler manages the text log files
198        formatter = logging.Formatter("%(asctime)-15s | %(levelname)s at %(location)s: %(message)s",
199                                                   "%d-%m-%Y %H:%M:%S")
200        if not self.logger.handlers:
201            handler = RotatingFileHandler(self.log_path, maxBytes=(50 * 1024 * 1024), backupCount=1)
202            handler.setLevel(log_level)
203            handler.setFormatter(formatter)
204            self.logger.addHandler(handler)
205
206        # to stdout
207        if output:
208            handler = logging.StreamHandler()
209            handler.setLevel(log_level)
210            handler.setFormatter(formatter)
211            self.logger.addHandler(handler)
212
213    def load_webhook(self, config):
214        """
215        Load webhook configuration
216
217        The webhook is configured in the database; but the logger may not
218        always have access to the database. So instead of setting it up at
219        init, this function must be called explicitly to enable it for this
220        logger instance.
221
222        :param config:  Configuration reader
223        :return:
224        """
225        if config.get("logging.slack.webhook"):
226            slack_handler = SlackLogHandler(config.get("logging.slack.webhook"))
227            slack_handler.setLevel(self.levels.get(config.get("logging.slack.level"), self.alert_level))
228            self.logger.addHandler(slack_handler)
229
230    def log(self, message, level=logging.INFO, frame=None):
231        """
232        Log message
233
234        :param message:  Message to log
235        :param level:  Severity level, should be a logger.* constant
236        :param frame:  Traceback frame. If no frame is given, it is
237        extrapolated
238        """
239        if type(frame) is traceback.StackSummary:
240            # Full strack was provided
241            stack = frame
242            frame = stack[-1]
243        else:
244            # Collect the stack (used by Slack)
245            stack = traceback.extract_stack()[:-2]
246
247        if frame is None:
248            # Use the last frame in the stack
249            frame = stack[-1]
250        else:
251            # Frame was provided; use it
252            pass
253
254        # Logging uses the location, Slack uses the full stack
255        location = frame.filename.split("/")[-1] + ":" + str(frame.lineno)
256        self.logger.log(level, message, extra={"location": location, "frame": frame, "stack": stack})
257
258    def debug(self, message, frame=None):
259        """
260        Log DEBUG level message
261
262        :param message: Message to log
263        :param frame:  Traceback frame relating to the error
264        """
265        self.log(message, logging.DEBUG, frame)
266
267    def info(self, message, frame=None):
268        """
269        Log INFO level message
270
271        :param message: Message to log
272        :param frame:  Traceback frame relating to the error
273        """
274        self.log(message, logging.INFO)
275
276    def warning(self, message, frame=None):
277        """
278        Log WARNING level message
279
280        :param message: Message to log
281        :param frame:  Traceback frame relating to the error
282        """
283        self.log(message, logging.WARN, frame)
284
285    def error(self, message, frame=None):
286        """
287        Log ERROR level message
288
289        :param message: Message to log
290        :param frame:  Traceback frame relating to the error
291        """
292        self.log(message, logging.ERROR, frame)
293
294    def critical(self, message, frame=None):
295        """
296        Log CRITICAL level message
297
298        :param message: Message to log
299        :param frame:  Traceback frame relating to the error
300        """
301        self.log(message, logging.CRITICAL, frame)
302
303    def fatal(self, message, frame=None):
304        """
305        Log FATAL level message
306
307        :param message: Message to log
308        :param frame:  Traceback frame relating to the error
309        """
310        self.log(message, logging.FATAL, frame)
class WebHookLogHandler(logging.handlers.HTTPHandler):
17class WebHookLogHandler(HTTPHandler):
18    """
19    Basic HTTPHandler for webhooks via standard log handling
20
21    In essence, an HTTPHandler that formats its payload as JSON.
22    """
23    server_name = ""
24
25    def __init__(self, url):
26        """
27        Initialise WebHook handler
28
29        :param str url:  URL to send log messages to
30        """
31        host = url.split("/")[2]
32        secure = url.lower().startswith("https")
33
34        super().__init__(host, url, method="POST", secure=secure)
35
36    def emit(self, record):
37        """
38        Emit a record
39
40        Send the record to the Web server as a percent-encoded dictionary
41        This is the `emit()` method of the original HTTPHandler; the only
42        change is that content is sent as JSON (which the webhooks expect)
43        instead of urlencoded data.
44
45        :param logging.LogRecord record:  Log record to send
46        """
47        try:
48            import http.client
49            host = self.host
50            if self.secure:
51                h = http.client.HTTPSConnection(host, context=self.context)
52            else:
53                h = http.client.HTTPConnection(host)
54            url = self.url
55            ############### CHANGED FROM ORIGINAL ###############
56            data = json.dumps(self.mapLogRecord(record))
57            #####################################################
58            if self.method == "GET":
59                if (url.find('?') >= 0):
60                    sep = '&'
61                else:
62                    sep = '?'
63                url = url + "%c%s" % (sep, data)
64            h.putrequest(self.method, url)
65            # support multiple hosts on one IP address...
66            # need to strip optional :port from host, if present
67            i = host.find(":")
68            if i >= 0:
69                host = host[:i]
70            # See issue #30904: putrequest call above already adds this header
71            # on Python 3.x.
72            # h.putheader("Host", host)
73            if self.method == "POST":
74                ############### CHANGED FROM ORIGINAL ###############
75                h.putheader("Content-type", "application/json")
76                #####################################################
77                h.putheader("Content-length", str(len(data)))
78            if self.credentials:
79                import base64
80                s = ('%s:%s' % self.credentials).encode('utf-8')
81                s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
82                h.putheader('Authorization', s)
83            h.endheaders()
84            if self.method == "POST":
85                h.send(data.encode('utf-8'))
86            h.getresponse()  # can't do anything with the result
87        except Exception:
88            self.handleError(record)

Basic HTTPHandler for webhooks via standard log handling

In essence, an HTTPHandler that formats its payload as JSON.

WebHookLogHandler(url)
25    def __init__(self, url):
26        """
27        Initialise WebHook handler
28
29        :param str url:  URL to send log messages to
30        """
31        host = url.split("/")[2]
32        secure = url.lower().startswith("https")
33
34        super().__init__(host, url, method="POST", secure=secure)

Initialise WebHook handler

Parameters
  • str url: URL to send log messages to
server_name = ''
def emit(self, record):
36    def emit(self, record):
37        """
38        Emit a record
39
40        Send the record to the Web server as a percent-encoded dictionary
41        This is the `emit()` method of the original HTTPHandler; the only
42        change is that content is sent as JSON (which the webhooks expect)
43        instead of urlencoded data.
44
45        :param logging.LogRecord record:  Log record to send
46        """
47        try:
48            import http.client
49            host = self.host
50            if self.secure:
51                h = http.client.HTTPSConnection(host, context=self.context)
52            else:
53                h = http.client.HTTPConnection(host)
54            url = self.url
55            ############### CHANGED FROM ORIGINAL ###############
56            data = json.dumps(self.mapLogRecord(record))
57            #####################################################
58            if self.method == "GET":
59                if (url.find('?') >= 0):
60                    sep = '&'
61                else:
62                    sep = '?'
63                url = url + "%c%s" % (sep, data)
64            h.putrequest(self.method, url)
65            # support multiple hosts on one IP address...
66            # need to strip optional :port from host, if present
67            i = host.find(":")
68            if i >= 0:
69                host = host[:i]
70            # See issue #30904: putrequest call above already adds this header
71            # on Python 3.x.
72            # h.putheader("Host", host)
73            if self.method == "POST":
74                ############### CHANGED FROM ORIGINAL ###############
75                h.putheader("Content-type", "application/json")
76                #####################################################
77                h.putheader("Content-length", str(len(data)))
78            if self.credentials:
79                import base64
80                s = ('%s:%s' % self.credentials).encode('utf-8')
81                s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
82                h.putheader('Authorization', s)
83            h.endheaders()
84            if self.method == "POST":
85                h.send(data.encode('utf-8'))
86            h.getresponse()  # can't do anything with the result
87        except Exception:
88            self.handleError(record)

Emit a record

Send the record to the Web server as a percent-encoded dictionary This is the emit() method of the original HTTPHandler; the only change is that content is sent as JSON (which the webhooks expect) instead of urlencoded data.

Parameters
  • logging.LogRecord record: Log record to send
class SlackLogHandler(WebHookLogHandler):
 91class SlackLogHandler(WebHookLogHandler):
 92    """
 93    Slack webhook log handler
 94    """
 95
 96    def mapLogRecord(self, record):
 97        """
 98        Format log message so it is compatible with Slack webhooks
 99
100        :param logging.LogRecord record: Log record
101        """
102        if record.levelno in (logging.ERROR, logging.CRITICAL):
103            color = "#FF0000"  # red
104        elif record.levelno == logging.WARNING:
105            color = "#DD7711"  # orange
106        else:
107            color = "#3CC619"  # green
108
109        # simple stack trace
110        if record.stack:
111            # this is the stack where the log was called
112            frames = record.stack
113        else:
114            # the last 9 frames are not specific to the exception (general logging code etc)
115            # the frame before that is where the exception was raised
116            frames = traceback.extract_stack()[:-9]
117        location = "`%s`" % "` → `".join([frame.filename.split("/")[-1] + ":" + str(frame.lineno) for frame in frames])
118
119        # prepare slack webhook payload
120        fields = [{
121            "title": "Stack trace:",
122            "value": location,
123            "short": False
124        }]
125
126        # try to read some metadata from the offending file
127        try:
128            with Path(record.frame.filename).open() as infile:
129                fields.append({
130                    "title": "Code (`" + record.frame.filename.split("/")[-1] + ":" + str(record.frame.lineno) + "`):",
131                    "value": "```" + infile.readlines()[record.frame.lineno - 1].strip() + "```",
132                    "short": False
133                })
134        except (IndexError, AttributeError):
135            # the file is not readable, or the line number is out of bounds
136            pass
137
138        return {
139            "text": ":bell: 4CAT %s logged on `%s`:" % (record.levelname.lower(), platform.uname().node),
140            "mrkdwn_in": ["text"],
141            "attachments": [{
142                "color": color,
143                "text": record.message,
144                "fields": fields
145            }]
146        }

Slack webhook log handler

def mapLogRecord(self, record):
 96    def mapLogRecord(self, record):
 97        """
 98        Format log message so it is compatible with Slack webhooks
 99
100        :param logging.LogRecord record: Log record
101        """
102        if record.levelno in (logging.ERROR, logging.CRITICAL):
103            color = "#FF0000"  # red
104        elif record.levelno == logging.WARNING:
105            color = "#DD7711"  # orange
106        else:
107            color = "#3CC619"  # green
108
109        # simple stack trace
110        if record.stack:
111            # this is the stack where the log was called
112            frames = record.stack
113        else:
114            # the last 9 frames are not specific to the exception (general logging code etc)
115            # the frame before that is where the exception was raised
116            frames = traceback.extract_stack()[:-9]
117        location = "`%s`" % "` → `".join([frame.filename.split("/")[-1] + ":" + str(frame.lineno) for frame in frames])
118
119        # prepare slack webhook payload
120        fields = [{
121            "title": "Stack trace:",
122            "value": location,
123            "short": False
124        }]
125
126        # try to read some metadata from the offending file
127        try:
128            with Path(record.frame.filename).open() as infile:
129                fields.append({
130                    "title": "Code (`" + record.frame.filename.split("/")[-1] + ":" + str(record.frame.lineno) + "`):",
131                    "value": "```" + infile.readlines()[record.frame.lineno - 1].strip() + "```",
132                    "short": False
133                })
134        except (IndexError, AttributeError):
135            # the file is not readable, or the line number is out of bounds
136            pass
137
138        return {
139            "text": ":bell: 4CAT %s logged on `%s`:" % (record.levelname.lower(), platform.uname().node),
140            "mrkdwn_in": ["text"],
141            "attachments": [{
142                "color": color,
143                "text": record.message,
144                "fields": fields
145            }]
146        }

Format log message so it is compatible with Slack webhooks

Parameters
  • logging.LogRecord record: Log record
class Logger:
149class Logger:
150    """
151    Logger
152
153    Sets up a rotating logger that writes to a log file
154    """
155    logger = None
156    log_path = None
157    print_logs = True
158    db = None
159    previous_report = 0
160    levels = {
161        "DEBUG": logging.DEBUG,
162        "INFO": logging.INFO,
163        "WARNING": logging.WARNING,
164        "ERROR": logging.ERROR,
165        "CRITICAL": logging.CRITICAL,
166        "FATAL": logging.FATAL
167    }
168    alert_level = "FATAL"
169
170    def __init__(self, log_path="4cat.log", logger_name='4cat', output=False, log_level="INFO"):
171        """
172        Set up log handler
173
174        :param str|Path filename:  File path that will be written to
175        :param str logger_name:  Identifier for logging context
176        :param bool output:  Whether to print logs to output
177        :param str log_level:  Messages at this level or below will be logged
178        """
179        if self.logger:
180            return
181        log_level = self.levels.get(log_level, logging.INFO)
182
183        self.print_logs = output
184
185        if type(log_path) is str:
186            core_config = CoreConfigManager()
187            log_path = core_config.get("PATH_LOGS").joinpath(log_path)
188
189        if not log_path.parent.exists():
190            log_path.parent.mkdir(parents=True)
191
192        self.log_path = log_path
193        self.previous_report = time.time()
194
195        self.logger = logging.getLogger(logger_name)
196        self.logger.setLevel(log_level)
197
198        # this handler manages the text log files
199        formatter = logging.Formatter("%(asctime)-15s | %(levelname)s at %(location)s: %(message)s",
200                                                   "%d-%m-%Y %H:%M:%S")
201        if not self.logger.handlers:
202            handler = RotatingFileHandler(self.log_path, maxBytes=(50 * 1024 * 1024), backupCount=1)
203            handler.setLevel(log_level)
204            handler.setFormatter(formatter)
205            self.logger.addHandler(handler)
206
207        # to stdout
208        if output:
209            handler = logging.StreamHandler()
210            handler.setLevel(log_level)
211            handler.setFormatter(formatter)
212            self.logger.addHandler(handler)
213
214    def load_webhook(self, config):
215        """
216        Load webhook configuration
217
218        The webhook is configured in the database; but the logger may not
219        always have access to the database. So instead of setting it up at
220        init, this function must be called explicitly to enable it for this
221        logger instance.
222
223        :param config:  Configuration reader
224        :return:
225        """
226        if config.get("logging.slack.webhook"):
227            slack_handler = SlackLogHandler(config.get("logging.slack.webhook"))
228            slack_handler.setLevel(self.levels.get(config.get("logging.slack.level"), self.alert_level))
229            self.logger.addHandler(slack_handler)
230
231    def log(self, message, level=logging.INFO, frame=None):
232        """
233        Log message
234
235        :param message:  Message to log
236        :param level:  Severity level, should be a logger.* constant
237        :param frame:  Traceback frame. If no frame is given, it is
238        extrapolated
239        """
240        if type(frame) is traceback.StackSummary:
241            # Full strack was provided
242            stack = frame
243            frame = stack[-1]
244        else:
245            # Collect the stack (used by Slack)
246            stack = traceback.extract_stack()[:-2]
247
248        if frame is None:
249            # Use the last frame in the stack
250            frame = stack[-1]
251        else:
252            # Frame was provided; use it
253            pass
254
255        # Logging uses the location, Slack uses the full stack
256        location = frame.filename.split("/")[-1] + ":" + str(frame.lineno)
257        self.logger.log(level, message, extra={"location": location, "frame": frame, "stack": stack})
258
259    def debug(self, message, frame=None):
260        """
261        Log DEBUG level message
262
263        :param message: Message to log
264        :param frame:  Traceback frame relating to the error
265        """
266        self.log(message, logging.DEBUG, frame)
267
268    def info(self, message, frame=None):
269        """
270        Log INFO level message
271
272        :param message: Message to log
273        :param frame:  Traceback frame relating to the error
274        """
275        self.log(message, logging.INFO)
276
277    def warning(self, message, frame=None):
278        """
279        Log WARNING level message
280
281        :param message: Message to log
282        :param frame:  Traceback frame relating to the error
283        """
284        self.log(message, logging.WARN, frame)
285
286    def error(self, message, frame=None):
287        """
288        Log ERROR level message
289
290        :param message: Message to log
291        :param frame:  Traceback frame relating to the error
292        """
293        self.log(message, logging.ERROR, frame)
294
295    def critical(self, message, frame=None):
296        """
297        Log CRITICAL level message
298
299        :param message: Message to log
300        :param frame:  Traceback frame relating to the error
301        """
302        self.log(message, logging.CRITICAL, frame)
303
304    def fatal(self, message, frame=None):
305        """
306        Log FATAL level message
307
308        :param message: Message to log
309        :param frame:  Traceback frame relating to the error
310        """
311        self.log(message, logging.FATAL, frame)

Logger

Sets up a rotating logger that writes to a log file

Logger( log_path='4cat.log', logger_name='4cat', output=False, log_level='INFO')
170    def __init__(self, log_path="4cat.log", logger_name='4cat', output=False, log_level="INFO"):
171        """
172        Set up log handler
173
174        :param str|Path filename:  File path that will be written to
175        :param str logger_name:  Identifier for logging context
176        :param bool output:  Whether to print logs to output
177        :param str log_level:  Messages at this level or below will be logged
178        """
179        if self.logger:
180            return
181        log_level = self.levels.get(log_level, logging.INFO)
182
183        self.print_logs = output
184
185        if type(log_path) is str:
186            core_config = CoreConfigManager()
187            log_path = core_config.get("PATH_LOGS").joinpath(log_path)
188
189        if not log_path.parent.exists():
190            log_path.parent.mkdir(parents=True)
191
192        self.log_path = log_path
193        self.previous_report = time.time()
194
195        self.logger = logging.getLogger(logger_name)
196        self.logger.setLevel(log_level)
197
198        # this handler manages the text log files
199        formatter = logging.Formatter("%(asctime)-15s | %(levelname)s at %(location)s: %(message)s",
200                                                   "%d-%m-%Y %H:%M:%S")
201        if not self.logger.handlers:
202            handler = RotatingFileHandler(self.log_path, maxBytes=(50 * 1024 * 1024), backupCount=1)
203            handler.setLevel(log_level)
204            handler.setFormatter(formatter)
205            self.logger.addHandler(handler)
206
207        # to stdout
208        if output:
209            handler = logging.StreamHandler()
210            handler.setLevel(log_level)
211            handler.setFormatter(formatter)
212            self.logger.addHandler(handler)

Set up log handler

Parameters
  • str|Path filename: File path that will be written to
  • str logger_name: Identifier for logging context
  • bool output: Whether to print logs to output
  • str log_level: Messages at this level or below will be logged
logger = None
log_path = None
print_logs = True
db = None
previous_report = 0
levels = {'DEBUG': 10, 'INFO': 20, 'WARNING': 30, 'ERROR': 40, 'CRITICAL': 50, 'FATAL': 50}
alert_level = 'FATAL'
def load_webhook(self, config):
214    def load_webhook(self, config):
215        """
216        Load webhook configuration
217
218        The webhook is configured in the database; but the logger may not
219        always have access to the database. So instead of setting it up at
220        init, this function must be called explicitly to enable it for this
221        logger instance.
222
223        :param config:  Configuration reader
224        :return:
225        """
226        if config.get("logging.slack.webhook"):
227            slack_handler = SlackLogHandler(config.get("logging.slack.webhook"))
228            slack_handler.setLevel(self.levels.get(config.get("logging.slack.level"), self.alert_level))
229            self.logger.addHandler(slack_handler)

Load webhook configuration

The webhook is configured in the database; but the logger may not always have access to the database. So instead of setting it up at init, this function must be called explicitly to enable it for this logger instance.

Parameters
  • config: Configuration reader
Returns
def log(self, message, level=20, frame=None):
231    def log(self, message, level=logging.INFO, frame=None):
232        """
233        Log message
234
235        :param message:  Message to log
236        :param level:  Severity level, should be a logger.* constant
237        :param frame:  Traceback frame. If no frame is given, it is
238        extrapolated
239        """
240        if type(frame) is traceback.StackSummary:
241            # Full strack was provided
242            stack = frame
243            frame = stack[-1]
244        else:
245            # Collect the stack (used by Slack)
246            stack = traceback.extract_stack()[:-2]
247
248        if frame is None:
249            # Use the last frame in the stack
250            frame = stack[-1]
251        else:
252            # Frame was provided; use it
253            pass
254
255        # Logging uses the location, Slack uses the full stack
256        location = frame.filename.split("/")[-1] + ":" + str(frame.lineno)
257        self.logger.log(level, message, extra={"location": location, "frame": frame, "stack": stack})

Log message

Parameters
  • message: Message to log
  • level: Severity level, should be a logger.* constant
  • frame: Traceback frame. If no frame is given, it is extrapolated
def debug(self, message, frame=None):
259    def debug(self, message, frame=None):
260        """
261        Log DEBUG level message
262
263        :param message: Message to log
264        :param frame:  Traceback frame relating to the error
265        """
266        self.log(message, logging.DEBUG, frame)

Log DEBUG level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error
def info(self, message, frame=None):
268    def info(self, message, frame=None):
269        """
270        Log INFO level message
271
272        :param message: Message to log
273        :param frame:  Traceback frame relating to the error
274        """
275        self.log(message, logging.INFO)

Log INFO level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error
def warning(self, message, frame=None):
277    def warning(self, message, frame=None):
278        """
279        Log WARNING level message
280
281        :param message: Message to log
282        :param frame:  Traceback frame relating to the error
283        """
284        self.log(message, logging.WARN, frame)

Log WARNING level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error
def error(self, message, frame=None):
286    def error(self, message, frame=None):
287        """
288        Log ERROR level message
289
290        :param message: Message to log
291        :param frame:  Traceback frame relating to the error
292        """
293        self.log(message, logging.ERROR, frame)

Log ERROR level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error
def critical(self, message, frame=None):
295    def critical(self, message, frame=None):
296        """
297        Log CRITICAL level message
298
299        :param message: Message to log
300        :param frame:  Traceback frame relating to the error
301        """
302        self.log(message, logging.CRITICAL, frame)

Log CRITICAL level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error
def fatal(self, message, frame=None):
304    def fatal(self, message, frame=None):
305        """
306        Log FATAL level message
307
308        :param message: Message to log
309        :param frame:  Traceback frame relating to the error
310        """
311        self.log(message, logging.FATAL, frame)

Log FATAL level message

Parameters
  • message: Message to log
  • frame: Traceback frame relating to the error