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