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)
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.
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
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
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
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
Inherited Members
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
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
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
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
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
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
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
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
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
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