| formatted indented on multi lines messages using handlers
"""
+"""
+to launch example:
+
+>> export TRG=SALOME-8.4.0
+>> cd .../sat5.1
+>> src/loggingSat.py
+>> AllTestLauncherSat.py -p 'test_???_logging*.py'
+>> export TRG=SALOME-8.4.0
+>> sat config $TRG -i KERNEL
+>> sat config -v LOCAL.log_dir
+>> rm -rf /volatile/wambeke/SAT5/SAT5_S840_MATIX24/LOGS
+>> sat prepare $TRG -p KERNEL
+>> sat log
+"""
+
import os
import sys
import logging as LOGI
+from logging.handlers import BufferingHandler
import pprint as PP
+import src.utilsSat as UTS
import src.coloringSat as COLS
_verbose = False
_loggerDefaultName = 'SatDefaultLogger'
_loggerUnittestName = 'SatUnittestLogger'
+_TRACE = LOGI.INFO - 2 # trace level is just below INFO
+LOGI.TRACE = _TRACE # only for coherency,
+#################################################################
+# utilities methods
+#################################################################
def indent(msg, nb, car=" "):
"""indent nb car (spaces) multi lines message except first one"""
s = msg.split("\n")
res = ("\n" + prefix).join(s)
return res
-def log(msg):
+def log(msg, force=False):
"""elementary log when no logging.Logger yet"""
prefix = "%s.log: " % _name
nb = len(prefix)
- if _verbose: print(prefix + indent(msg, nb))
+ if _verbose or force:
+ print(prefix + indent(msg, nb))
log("import logging on %s" % LOGI.__file__)
-def dirLogger(logger):
- logger.info('dir(logger name=%s):\n' % logger.name, PP.pformat(dir(logger)))
+def getStrDirLogger(logger):
+ """
+ Returns multi line string for logger description, with dir(logger).
+ Used for debug
+ """
+ lgr = logger # shortcut
+ msg = "%s(name=%s, dateLogger=%s):\n%s\n"
+ cName = lgr.__class__.__name__
+ res = msg % (cName, lgr.name, lgr.dateLogger, PP.pformat(dir(lgr)))
+ return res
+def getStrHandler(handler):
+ """
+ Returns one line string for handler description
+ (as inexisting __repr__)
+ to avoid create inherited classe(s) handler
+ """
+ h = handler # shortcut
+ msg = "%s(name=%s)"
+ cName = h.__class__.__name__
+ res = msg % (cName, h.get_name())
+ return res
+
+def getStrShort(msg):
+ """Returns short string for msg (as first caracters without line feed"""
+ res = msg.replace("\n", "//")[0:30]
+ return res
+
+def getStrLogRecord(logRecord):
+ """
+ Returns one line string for simple logging LogRecord description
+ """
+ msg = "LogRecord(level='%s', msg='%s...')"
+ shortMsg = getStrShort(logRecord.msg)
+ levelName = COLS.cleanColors(logRecord.levelname).replace(" ", "")
+ res = msg % (levelName, shortMsg)
+ return res
+def getListOfStrLogRecord(listOfLogRecord):
+ """
+ Returns one line string for logging LogRecord description
+ """
+ res = [getStrLogRecord(l) for l in listOfLogRecord]
+ return res
+
+#################################################################
+# salometools logger classes
+#################################################################
class LoggerSat(LOGI.Logger):
"""
- inherited class logging.Logger for logger salomeTools
+ Inherited class logging.Logger for logger salomeTools
| add a level TRACE as log.trace(msg)
| below log.info(msg)
| see: /usr/lib64/python2.7/logging/__init__.py etc.
"""
- _TRACE = LOGI.INFO - 2 # just below
-
def __init__(self, name, level=LOGI.INFO):
"""
Initialize the logger with a name and an optional level.
"""
super(LoggerSat, self).__init__(name, level)
- LOGI.addLevelName(self._TRACE, "TRACE")
- # LOGI.TRACE = self._TRACE # only for coherency,
+ LOGI.addLevelName(_TRACE, "TRACE")
+ self.dateLogger = "NoDateLogger"
+ self.closed = False
+
+ def close(self):
+ """
+ final stuff for logger, done at end salomeTools
+ flushed and closed xml files have to be not overriden/appended
+ """
+ if self.closed:
+ raise Exception("logger closed yet: %s" % self)
+ log("close stuff logger %s" % self, True) # getStrDirLogger(self)
+ for handl in self.handlers:
+ log("close stuff handler %s" % getStrHandler(handl), True)
+ handl.close() # Tidy up any resources used by the handler.
+ # todo etc
+ self.closed = True # done at end sat, flushed closed xml files.
+ return
+
+ def __repr__(self):
+ """one line string representation"""
+ msg = "%s(name=%s, dateLogger=%s, handlers=%s)"
+ cName = self.__class__.__name__
+ h = [getStrHandler(h) for h in self.handlers]
+ h = "[" + ", ".join(h) + "]"
+ res = msg % (cName, self.name, self.dateLogger, h)
+ return res
def trace(self, msg, *args, **kwargs):
"""
Log 'msg % args' with severity '_TRACE'.
- To pass exception information, use the keyword argument exc_info with
- a true value, e.g.
-
- logger.trace("Houston, we have a %s", "long trace to follow")
+ | To pass exception information,
+ | use the keyword argument exc_info with a true value
+ | >> logger.trace("Houston, we have a %s", "long trace to follow")
"""
+ log("trace stuff logger %s dateLogger %s", True)
if self.isEnabledFor(self._TRACE):
self._log(self._TRACE, msg, args, **kwargs)
- def isEnabledFor(self, level):
+ def xx_isEnabledFor(self, level):
"""
Is this logger enabled for level 'level'?
- currently not modified from logging.Logger class
+ currently not modified from logging.Logger class,
+ here only for call log debug.
"""
log("logger %s isEnabledFor %i>=%i" % (self.name, level, self.getEffectiveLevel()))
if self.manager.disable >= level:
return 0
return level >= self.getEffectiveLevel()
+
+#################################################################
class DefaultFormatter(LOGI.Formatter):
# to set color prefix, problem with indent format as
res = color + levelname + "<reset>"
nb = len(levelname)
res = res + " "*(8-nb) # 8 as len("CRITICAL")
- # print "'%s'" % res
+ # log("setColorLevelname'%s'" % res)
return res
+#################################################################
class UnittestFormatter(LOGI.Formatter):
def format(self, record):
# print "", record.levelname #type(record), dir(record)
return COLS.toColor(res)
+#################################################################
class UnittestStream(object):
"""
write my stream class
def __str__(self):
return self._logs
+#################################################################
+class XmlHandler(BufferingHandler):
+ """
+ log outputs in memory as BufferingHandler.
+ Write ElementTree in file and flush are done once
+ when method close is called, to generate xml file.
+
+ see: https://docs.python.org/2/library/logging.handlers.html
+ """
+ def __init__(self, capacity):
+ super(XmlHandler, self).__init__(capacity)
+ self._target_file = None
+ self._config = None
+
+ def set_target_file(self, filename):
+ """
+ filename is file name xml with path
+ supposedly non existing, no overwrite accepted
+ """
+ if os.path.exists(filename):
+ msg = "XmlHandler target file %s existing yet" % filename
+ raise Exception(msg)
+ self._target_file = filename
+ def set_config(self, config):
+ """
+ config is supposedly non existing, no overwrite accepted
+ """
+ if self._config is not None:
+ msg = "XmlHandler target config existing yet"
+ raise Exception(msg)
+ self._config = config
+
+ def close(self):
+ """prepare ElementTree from existing logs and write xml file"""
+ import src.xmlManager as XMLMGR # avoid import cross utilsSat
+ targetFile = self._target_file
+ config = self._config
+
+ # TODO for degug
+ log("XmlHandler to xml file\n%s" % PP.pformat(getListOfStrLogRecord(self.buffer)), True)
+
+ if os.path.exists(targetFile):
+ msg = "XmlHandler target file %s existing yet" % targetFile
+
+ xmlFile = XMLMGR.XmlLogFile(targetFile, "SATcommand", attrib = {"application" : config.VARS.application})
+ xmlFile.write_tree()
+
+ super(XmlHandler, self).close() # zaps the buffer to empty
+
+#################################################################
+# methods to define two LoggerSat instances in salomeTools,
+# no more need
+#################################################################
def initLoggerAsDefault(logger, fmt=None, level=None):
"""
init logger as prefixed message and indented message if multi line
"""
log("initLoggerAsDefault name=%s\nfmt='%s' level='%s'" % (logger.name, fmt, level))
handler = LOGI.StreamHandler(sys.stdout) # Logging vers console
+ handler.set_name(logger.name + "_console")
if fmt is not None:
# formatter = LOGI.Formatter(fmt, "%Y-%m-%d %H:%M:%S")
formatter = DefaultFormatter(fmt, "%y-%m-%d %H:%M:%S")
log("initLoggerAsUnittest name=%s\nfmt='%s' level='%s'" % (logger.name, fmt, level))
stream = UnittestStream()
handler = LOGI.StreamHandler(stream) # Logging vers stream
+ handler.set_name(logger.name + "_unittest")
if fmt is not None:
# formatter = LOGI.Formatter(fmt, "%Y-%m-%d %H:%M:%S")
formatter = UnittestFormatter(fmt, "%Y-%m-%d %H:%M:%S")
else:
logger.setLevel(logger.DEBUG)
+
def setFileHandler(logger, config):
"""
add file handler to logger to set log files
| ~/LOGS/OUT/micro_20180510_140607_clean_lenovo.txt
| etc.
"""
- import src.debug as DBG # avoid cross import
- DBG.write("setFileHandler", logger.handlers, True)
- DBG.write("setFileHandler", config.VARS, True)
+ #import src.debug as DBG # avoid cross import
+ log("setFileHandler %s" % logger, True)
+ log("setFileHandler config\n%s" % PP.pformat(dict(config.VARS)), True)
+ log("setFileHandler TODO set log_dir config.LOCAL.log_dir", True)
+ log_dir = "TMP" # TODO for debug config.LOCAL.log_dir # files xml
+ log_dir_out = os.path.join(log_dir, "OUT") # files txt
+ UTS.ensure_path_exists(log_dir)
+ UTS.ensure_path_exists(log_dir_out)
+ datehour = config.VARS.datehour
+ cmd = config.VARS.command
+ hostname = config.VARS.hostname
+ nameFileXml = "%s_%s_%s.xml" % (datehour, cmd, hostname)
+ nameFileTxt = "%s_%s_%s.txt" % (datehour, cmd, hostname)
+ fileXml = os.path.join(log_dir, nameFileXml)
+ fileTxt = os.path.join(log_dir_out, nameFileTxt)
+
+ nbhandl = len(logger.handlers) # number of current handlers
+ if nbhandl == 1: # first main command
+ # Logging vers file xml
+
+ handler = XmlHandler(1000) # log outputs in memory
+ handler.setLevel(LOGI.INFO)
+ handler.set_name(nameFileXml)
+ handler.set_target_file(fileXml)
+ handler.set_config(config)
+
+ fmt = '%(asctime)s :: %(levelname)s :: %(message)s'
+ formatter = UnittestFormatter(fmt, "%Y-%m-%d %H:%M:%S")
+
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+
+ # Logging vers file txt
+ handler = LOGI.FileHandler(fileTxt)
+ handler.setLevel(LOGI.TRACE)
+ handler.set_name(nameFileTxt)
+
+ fmt = '%(asctime)s :: %(levelname)s :: %(message)s'
+ formatter = UnittestFormatter(fmt, "%Y-%m-%d %H:%M:%S")
+
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+
+ if nbhandl > 1: # secondary micro command
+ log("setFileHandler micro command %s" % config.VARS.command, True)
+
+ log("setFileHandler %s" % logger, True)
+
+
def getDefaultLogger():
log("getDefaultLogger %s" % _loggerDefaultName)
# case multithread may be problem as not LOGI._acquireLock()
LOGI.setLoggerClass(previousClass)
return res
+
def getUnittestLogger():
log("getUnittestLogger %s" % _loggerUnittestName)
# case multithread may be problem as not LOGI._acquireLock()
res = LOGI.getLogger(_loggerUnittestName)
LOGI.setLoggerClass(previousClass)
return res
+
+#################################################################
+# small tests as demonstration, see unittest also
+#################################################################
def testLogger_1(logger):
"""small test"""
- # dirLogger(logger)
+ # print getStrDirLogger(logger)
logger.debug('test logger debug')
logger.trace('test logger trace')
logger.info('test logger info')
logger.info('\ntest logger info: no indent\n- second line\n- third line\n')
logger.warning('test logger warning:\n- second line\n- third line')
+
def testMain():
print("\n**** DEFAULT logger")
logdef = getDefaultLogger()
from colorama import Style as ST
print("this is unconditionally %scolored in green%s !!!" % (FG.GREEN, ST.RESET_ALL))
+
+
+#################################################################
+# in production, or not (if __main__)
+#################################################################
if __name__ == "__main__":
+ # for example, not in production
# get path to salomeTools sources
satdir = os.path.dirname(os.path.dirname(__file__))
# Make the src & commands package accessible from all code
sys.path.insert(0, satdir)
- testMain()
+ testMain()
+ # here we have sys.exit()
else:
+ # in production
# get two LoggerSat instance used in salomeTools, no more needed.
_loggerDefault = getDefaultLogger()
_loggerUnittest = getUnittestLogger()
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-Utilities to manage write/read xml logging files
+Utilities to manage write/read xml salometools logging files
| Usage:
| >> import src.xmlManager as XMLMGR
import src.utilsSat as UTS
+##############################################################################
+# classes to write and read xml salometools logging files
+##############################################################################
class XmlLogFile(object):
"""
Class to manage writing in salomeTools xml log file
:param attrib: (dict) The attrib to append
"""
self.xmlroot.find(node_name).attrib.update(attrib)
+
+
+ def put_initial_xml_fields(self):
+ """
+ Called at class initialization: Put all fields
+ corresponding to the command context (user, time, ...)
+ """
+ # command name
+ self.xmlFile.add_simple_node("Site", attrib={"command" :
+ self.config.VARS.command})
+ # version of salomeTools
+ self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
+ self.config.INTERNAL.sat_version})
+ # machine name on which the command has been launched
+ self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
+ self.config.VARS.hostname})
+ # Distribution of the machine
+ self.xmlFile.append_node_attrib("Site", attrib={"OS" :
+ self.config.VARS.dist})
+ # The user that have launched the command
+ self.xmlFile.append_node_attrib("Site", attrib={"user" :
+ self.config.VARS.user})
+ # The time when command was launched
+ Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
+ date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
+ self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
+ date_hour})
+ # The application if any
+ if "APPLICATION" in self.config:
+ self.xmlFile.append_node_attrib("Site",
+ attrib={"application" : self.config.VARS.application})
+ # The initialization of the trace node
+ self.xmlFile.add_simple_node("Log",text="")
+ # The system commands logs
+ self.xmlFile.add_simple_node("OutLog",
+ text=os.path.join("OUT", self.txtFileName))
+ # The initialization of the node where
+ # to put the links to the other sat commands that can be called by any
+ # command
+ self.xmlFile.add_simple_node("Links")
+
+ def add_link(self,
+ log_file_name,
+ command_name,
+ command_res,
+ full_launched_command):
+ """Add a link to another log file.
+
+ :param log_file_name str: The file name of the link.
+ :param command_name str: The name of the command linked.
+ :param command_res str: The result of the command linked. "0" or "1"
+ :parma full_launched_command str: The full lanch command
+ ("sat command ...")
+ """
+ xmlLinks = self.xmlFile.xmlroot.find("Links")
+ src.xmlManager.add_simple_node(xmlLinks,
+ "link",
+ text = log_file_name,
+ attrib = {"command" : command_name,
+ "passed" : command_res,
+ "launchedCommand" : full_launched_command})
+
+ def write(self, message, level=None, screenOnly=False):
+ """
+ function used in the commands
+ to print in the terminal and the log file.
+
+ :param message str: The message to print.
+ :param level int: The output level corresponding
+ to the message 0 < level < 6.
+ :param screenOnly boolean: if True, do not write in log file.
+ """
+ # do not write message starting with \r to log file
+ if not message.startswith("\r") and not screenOnly:
+ self.xmlFile.append_node_text("Log",
+ printcolors.cleancolor(message))
+
+ # get user or option output level
+ current_output_verbose_level = self.config.USER.output_verbose_level
+ if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
+ # clean the message color if the terminal is redirected by user
+ # ex: sat compile appli > log.txt
+ message = printcolors.cleancolor(message)
+
+ # Print message regarding the output level value
+ if level:
+ if level <= current_output_verbose_level and not self.silentSysStd:
+ sys.stdout.write(message)
+ else:
+ if self.default_level <= current_output_verbose_level and not self.silentSysStd:
+ sys.stdout.write(message)
+ self.flush()
+ def error(self, message):
+ """Print an error.
+
+ :param message str: The message to print.
+ """
+ # Print in the log file
+ self.xmlFile.append_node_text("traces", _('ERROR:') + message)
+
+ # Print in the terminal and clean colors if the terminal
+ # is redirected by user
+ if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
+ sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
+ else:
+ sys.stderr.write(_('ERROR:') + message)
+
+
+ def end_write(self, attribute):
+ """
+ Called just after command end: Put all fields
+ corresponding to the command end context (time).
+ Write the log xml file on the hard drive.
+ And display the command to launch to get the log
+
+ :param attribute dict: the attribute to add to the node "Site".
+ """
+ # Get current time (end of command) and format it
+ dt = datetime.datetime.now()
+ Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
+ t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
+ tf = dt
+ delta = tf - t0
+ total_time = timedelta_total_seconds(delta)
+ hours = int(total_time / 3600)
+ minutes = int((total_time - hours*3600) / 60)
+ seconds = total_time - hours*3600 - minutes*60
+ # Add the fields corresponding to the end time
+ # and the total time of command
+ endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
+ self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
+ self.xmlFile.append_node_attrib("Site",
+ attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
+
+ # Add the attribute passed to the method
+ self.xmlFile.append_node_attrib("Site", attrib=attribute)
+
+ # Call the method to write the xml file on the hard drive
+ self.xmlFile.write_tree(stylesheet = "command.xsl")
+
+ # Dump the config in a pyconf file in the log directory
+ logDir = src.get_log_path(self.config)
+ dumpedPyconfFileName = (self.config.VARS.datehour
+ + "_"
+ + self.config.VARS.command
+ + ".pyconf")
+ dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
+ try:
+ f = open(dumpedPyconfFilePath, 'w')
+ self.config.__save__(f)
+ f.close()
+ except IOError:
+ pass
+
+
+##############################################################################
class ReadXmlFile(object):
"""
Class to manage reading of an xml log file
"""
return self.xmlroot.find(node).text
+##############################################################################
+# utilities method
+##############################################################################
def add_simple_node(root_node, node_name, text=None, attrib={}):
"""Add a node with some attibutes and text to the root node.
return node
return None
-
def write_report(filename, xmlroot, stylesheet):
"""Writes a report file from a XML tree.