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

Basic HTTPHandler for webhooks via standard log handling

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

WebHookLogHandler(url)
23    def __init__(self, url):
24        """
25        Initialise WebHook handler
26
27        :param str url:  URL to send log messages to
28        """
29        host = url.split("/")[2]
30        secure = url.lower().startswith("https")
31
32        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):
34    def emit(self, record):
35        """
36        Emit a record
37
38        Send the record to the Web server as a percent-encoded dictionary
39        This is the `emit()` method of the original HTTPHandler; the only
40        change is that content is sent as JSON (which the webhooks expect)
41        instead of urlencoded data.
42
43        :param logging.LogRecord record:  Log record to send
44        """
45        try:
46            import http.client
47            host = self.host
48            if self.secure:
49                h = http.client.HTTPSConnection(host, context=self.context)
50            else:
51                h = http.client.HTTPConnection(host)
52            url = self.url
53            ############### CHANGED FROM ORIGINAL ###############
54            data = json.dumps(self.mapLogRecord(record))
55            #####################################################
56            if self.method == "GET":
57                if (url.find('?') >= 0):
58                    sep = '&'
59                else:
60                    sep = '?'
61                url = url + "%c%s" % (sep, data)
62            h.putrequest(self.method, url)
63            # support multiple hosts on one IP address...
64            # need to strip optional :port from host, if present
65            i = host.find(":")
66            if i >= 0:
67                host = host[:i]
68            # See issue #30904: putrequest call above already adds this header
69            # on Python 3.x.
70            # h.putheader("Host", host)
71            if self.method == "POST":
72                ############### CHANGED FROM ORIGINAL ###############
73                h.putheader("Content-type", "application/json")
74                #####################################################
75                h.putheader("Content-length", str(len(data)))
76            if self.credentials:
77                import base64
78                s = ('%s:%s' % self.credentials).encode('utf-8')
79                s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
80                h.putheader('Authorization', s)
81            h.endheaders()
82            if self.method == "POST":
83                h.send(data.encode('utf-8'))
84            h.getresponse()  # can't do anything with the result
85        except Exception:
86            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):
 89class SlackLogHandler(WebHookLogHandler):
 90    """
 91    Slack webhook log handler
 92    """
 93
 94    def mapLogRecord(self, record):
 95        """
 96        Format log message so it is compatible with Slack webhooks
 97
 98        :param logging.LogRecord record: Log record
 99        """
100        if record.levelno in (logging.ERROR, logging.CRITICAL):
101            color = "#FF0000"  # red
102        elif record.levelno == logging.WARNING:
103            color = "#DD7711"  # orange
104        else:
105            color = "#3CC619"  # green
106
107        # simple stack trace
108        if record.stack:
109            # this is the stack where the log was called
110            frames = record.stack
111        else:
112            # the last 9 frames are not specific to the exception (general logging code etc)
113            # the frame before that is where the exception was raised
114            frames = traceback.extract_stack()[:-9]
115        location = "`%s`" % "` → `".join([frame.filename.split("/")[-1] + ":" + str(frame.lineno) for frame in frames])
116
117        # prepare slack webhook payload
118        fields = [{
119            "title": "Stack trace:",
120            "value": location,
121            "short": False
122        }]
123
124        # try to read some metadata from the offending file
125        try:
126            with Path(record.frame.filename).open() as infile:
127                fields.append({
128                    "title": "Code (`" + record.frame.filename.split("/")[-1] + ":" + str(record.frame.lineno) + "`):",
129                    "value": "```" + infile.readlines()[record.frame.lineno - 1].strip() + "```",
130                    "short": False
131                })
132        except (IndexError, AttributeError):
133            # the file is not readable, or the line number is out of bounds
134            pass
135
136        return {
137            "text": ":bell: 4CAT %s logged on `%s`:" % (record.levelname.lower(), platform.uname().node),
138            "mrkdwn_in": ["text"],
139            "attachments": [{
140                "color": color,
141                "text": record.message,
142                "fields": fields
143            }]
144        }

Slack webhook log handler

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

Format log message so it is compatible with Slack webhooks

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

Log FATAL level message

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