import traceback
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
try:
from accpac import *
except ImportError as e:
# This happens when the tools are imported outside of the Extender env.
# We can pass to let the tool do its work (likely sphinx making docs).
pass
from extools.error_stack import consume_errors
__exm_loggers = {}
[docs]def logger_for_module(module_name, level=None, box=None, **kwargs):
global __exm_loggers
filestem = module_name.split(".")[0]
log_path = Path(getOrgPath(), "{}.log".format(filestem))
log_debug_path = Path(getOrgPath(), "{}.debug".format(filestem))
if not level:
level = ExMessages.INFO
if log_debug_path.exists():
level = ExMessages.DEBUG
if filestem not in __exm_loggers:
__exm_loggers[filestem] = ExMessages(
module_name, level=level, log_path=str(log_path),
box=box, **kwargs)
else:
return ExMessages(module_name, level=level, box=box,
handler=__exm_loggers[filestem].handler, **kwargs)
return __exm_loggers[filestem]
[docs]class ExMessages(object):
"""A logger like object for writing messages for the user.
The ExtenderMessageWriter acts like a logger, allowing a developer
to add messages that are only displayed to the user if the current
level is greater than or equal to the message level being called.
Messages at debug and below, as well as those at error or above,
support displaying the last exception traceback to make debugging
easier.
:param name: the name to log under.
:type name: str
:param level: the level at and below which to display messages.
:type level: int
:param log_path: the path of a log file to write to.
:type log_path: str
:param programs: the list of programs for which to display
messages. For example, if programs were ["OE1100", ] then messages
will only be displayed if the Order Entry program is currently
running.
:type programs: list
:param box: indicates whether to show a message box, add a message to the
Sage message stack, or suppress UI messages. Defaults to True.
:type box: True (showMessageBox), False (message stack), None (suppress)
:param disabled: disable all messages and logging.
Defaults to False.
:type disabled: bool
"""
"""Supported Levels"""
PANIC = 0
CRITICAL = 1
ERROR = 5
WARNING = 10
INFO = 15
DEBUG = 20
RAW = 25
LEVELS = (
PANIC,
CRITICAL,
ERROR,
WARNING,
INFO,
DEBUG,
RAW,
)
"""Supported log levels in decreasing order of severity."""
# Level names and log method lookup
_LEVEL_INFO= {
PANIC: ("Panic", "critical", ),
CRITICAL: ("Critical", "critical", ),
ERROR: ("Error", "error", ),
WARNING: ("Warning", "warning", ),
INFO: ("Info", "info", ),
DEBUG: ("Debug", "debug", ),
RAW: ("Raw", "debug", ),
}
YES_NO_DIALOG = 0x04
YES_NO_DIALOG_YES = 6
YES_NO_DIALOG_NO = 7
def __init__(self, name, level=None, log_path=None, programs=[], box=True,
disabled=False, key="", handler=None):
"""Get a new ExMessages instance."""
if not level:
level = self.INFO
self.level = level
self.level_info = self._LEVEL_INFO[level]
self.level_name = self.level_info[0]
self.name = name
self.disabled = disabled
self.programs = programs
self.box = box
self.key = key
self.handler = handler
self.log = None
self.log_path = log_path
self.log_level = getattr(logging, self.level_info[1].upper())
if self.log_path or self.handler:
self._setup_log()
def _setup_log(self):
self.log = logging.getLogger(self.name)
if not self.handler:
self.handler = RotatingFileHandler(
filename=str(self.log_path),
backupCount=1,
maxBytes=10*1024*1024)
self.handler.setFormatter(logging.Formatter(
fmt='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
datefmt='%Y-%m-%dT%H:%M:%S',))
self.log.addHandler(self.handler)
self.log.setLevel(self.log_level)
def _write(self, level, msg):
if self.disabled or (self.programs and program not in self.programs):
return None
name = self.name
if self.key:
name = "{}.{}".format(name, self.key)
msg_w_name = "{}\n\n{}".format(name, msg)
if level <= self.level:
if level == self.DEBUG:
msg_w_name = "DEBUG {}\n\n{}".format(rotoID, msg_w_name)
if self.box:
showMessageBox(msg_w_name)
elif self.box is not None:
if level <= self.ERROR:
error(msg_w_name)
elif level <= self.WARNING:
warning(msg_w_name)
else:
message(msg_w_name)
if self.log:
log_func = getattr(self.log, self._LEVEL_INFO[level][1])
log_func("{} {}".format(self.key, msg))
return msg
[docs] def panic(self, msg, exc_info=None):
"""Display and log a panic message.
:param msg: message to write.
:type msg: str
:param exc_info: include last exception backtrace?
:type exc_info: bool
:rtype: None
"""
if exc_info:
msg = "\n".join([msg, traceback.format_exc(), ])
self._write(self.PANIC, msg)
[docs] def crit(self, msg, exc_info=None):
"""Display and log a critical message.
:param msg: message to write.
:type msg: str
:param exc_info: include last exception backtrace?
:type exc_info: bool
:rtype: None
"""
if exc_info:
msg = "\n".join([msg, traceback.format_exc(), ])
self._write(self.CRITICAL, msg)
[docs] def error(self, msg, exc_info=None):
"""Display and log an error message.
:param msg: message to write.
:type msg: str
:param exc_info: include last exception backtrace?
:type exc_info: bool
:rtype: None
"""
if exc_info:
msg = "\n".join([msg, traceback.format_exc(), ])
self._write(self.ERROR, msg)
[docs] def warn(self, msg):
"""Display and log a warning message.
:param msg: message to write.
:type msg: str
:rtype: None
"""
self._write(self.WARNING, msg)
[docs] def info(self, msg):
"""Display and log an info message.
:param msg: message to write.
:type msg: str
:rtype: None
"""
self._write(self.INFO, msg)
[docs] def debug(self, msg, exc_info=False):
"""Display and log a debug message.
:param msg: message to write.
:type msg: str
:param exc_info: include last exception backtrace?
:type exc_info: bool
:rtype: None
"""
if exc_info:
msg = "\n".join([msg, traceback.format_exc(), ])
self._write(self.DEBUG, msg)
[docs] def raw(self, msg, exc_info=False):
"""Display and log raw output.
:param msg: message to write.
:type msg: str
:param exc_info: include last exception backtrace?
:type exc_info: bool
:rtype: None
"""
msg = "RAW {}\n---------\n{}".format(rotoID, msg)
if exc_info:
msg = "\n".join([msg, traceback.format_exc(), ])
self._write(self.RAW, msg)
[docs] @classmethod
def prompt(self, title, message):
"""Display a Yes/No dialog prompt.
:param title: The message box title.
:type title: str
:param message: The prompt to display.
:type message: str
:returns: True if User selects Yes, else No
:rtype: bool
"""
answer = ask(message, title, self.YES_NO_DIALOG)
if answer == self.YES_NO_DIALOG_YES:
return True
return False
[docs] def debug_error_stack(self):
"""Write the contents of the error stack to log as debug messages
and clear the stack."""
for priority, message in consume_errors():
self.debug("Error Stack {}: {}".format(priority, message))
return True