]> SALOME platform Git repositories - tools/sat.git/commitdiff
Salome HOME
begin logging xml files
authorChristian Van Wambeke <christian.van-wambeke@cea.fr>
Tue, 15 May 2018 14:49:11 +0000 (16:49 +0200)
committerChristian Van Wambeke <christian.van-wambeke@cea.fr>
Tue, 15 May 2018 14:49:11 +0000 (16:49 +0200)
data/local.pyconf
data/local_original.pyconf [new file with mode: 0644]
data/local_wambeke_cea.pyconf [new file with mode: 0644]
data/local_wambeke_home.pyconf [new file with mode: 0644]
sat
src/loggingSat.py
src/product.py
src/xmlManager.py
test/test_024_logging.py

index 7e6644a7f203dbbf9ed1bc56f9a217bdde5f7e54..ed70f4b40c94dfc5e471cc7a6a407557f776171f 100644 (file)
     project_file_paths :
     [
     "/volatile/wambeke/SAT5/SAT5_S840_MATIX24/SAT_SALOME/salome.pyconf",
-    # "/home/uranietm/proJET/saTJOBS/saT5/uranie.pyconf",
-    # cloned 2017/12 for matix 
-    ##"/home/matix/GitRepo/uranie/saT5/uranie.pyconf",
     "/volatile/wambeke/SAT5/SAT_MATIX/matix.pyconf"
-    "/home/christian/SAT_SALOME/salome.pyconf"
-    "/home/christian/SAT_MATIX/matix.pyconf"
-    #"/home/christian/SAT_MATIX"
     ]
   }
diff --git a/data/local_original.pyconf b/data/local_original.pyconf
new file mode 100644 (file)
index 0000000..7755af0
--- /dev/null
@@ -0,0 +1,16 @@
+
+  LOCAL :
+  {
+    base : 'default'
+    workdir : 'default'
+    log_dir : 'default'
+    archive_dir : 'default'
+    VCS : None
+    tag : None
+  }
+  PROJECTS :
+  {
+    project_file_paths :
+    [
+    ]
+  }
diff --git a/data/local_wambeke_cea.pyconf b/data/local_wambeke_cea.pyconf
new file mode 100644 (file)
index 0000000..e62c609
--- /dev/null
@@ -0,0 +1,21 @@
+
+  LOCAL :
+  {
+    base : 'default'
+    workdir : 'default'
+    log_dir : 'default'
+    archive_dir : 'default'
+    VCS : None
+    tag : None
+  }
+  PROJECTS :
+  {
+    project_file_paths :
+    [
+    "/volatile/wambeke/SAT5/SAT5_S840_MATIX24/SAT_SALOME/salome.pyconf",
+    # "/home/uranietm/proJET/saTJOBS/saT5/uranie.pyconf",
+    # cloned 2017/12 for matix 
+    # "/home/matix/GitRepo/uranie/saT5/uranie.pyconf",
+    # "/volatile/wambeke/SAT5/SAT_MATIX/matix.pyconf"
+    ]
+  }
diff --git a/data/local_wambeke_home.pyconf b/data/local_wambeke_home.pyconf
new file mode 100644 (file)
index 0000000..c91da53
--- /dev/null
@@ -0,0 +1,18 @@
+
+  LOCAL :
+  {
+    base : 'default'
+    workdir : 'default'
+    log_dir : 'default'
+    archive_dir : 'default'
+    VCS : None
+    tag : None
+  }
+  PROJECTS :
+  {
+    project_file_paths :
+    [
+    "/home/christian/SAT_SALOME/salome.pyconf"
+    "/home/christian/SAT_MATIX/matix.pyconf"
+    ]
+  }
diff --git a/sat b/sat
index 9ac9b8f2a607f67ab2702426337811bf7d392d77..f36b1cbca68697781fc2186598134f1edfddadfe 100755 (executable)
--- a/sat
+++ b/sat
@@ -60,17 +60,20 @@ if __name__ == "__main__":
       else:
         # warning as known problem
         logger.warning("<red>sat exit code: %s<reset>" % returnCode) # KO have to say why
+      logger.close()
       sys.exit(returnCode.toSys())
       
     except Exception as e:
       # error as may be unknown problem
       # verbose debug message with traceback if developers
       msg = "Exception raised for execute_cli(%s):\n" % args
-      logger.critical(DBG.format_color_exception(msg))  
+      logger.critical(DBG.format_color_exception(msg)) 
+      logger.close()
       sys.exit(KOSYS)
 
 else:
     logger.critical("forbidden/unexpected mode for __name__ '%s'" % __name__)
+    logger.close()
     sys.exit(KOSYS)
             
 
index 29e7e9403941a2a79953e873eeede8773023d23f..9286badf170445db560c1f7bca4d0e0b90c4ca2c 100755 (executable)
@@ -23,10 +23,27 @@ salomeTools logger. using logging package
 |      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
@@ -34,7 +51,12 @@ _name = "loggingSat"
 _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")
@@ -51,23 +73,68 @@ def indentUnittest(msg, prefix=" | "):
   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)
@@ -77,38 +144,64 @@ class LoggerSat(LOGI.Logger):
   | 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 
@@ -141,10 +234,11 @@ class DefaultFormatter(LOGI.Formatter):
     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)
@@ -154,6 +248,7 @@ class UnittestFormatter(LOGI.Formatter):
     return COLS.toColor(res)
 
 
+#################################################################
 class UnittestStream(object):
   """
   write my stream class
@@ -184,7 +279,60 @@ class UnittestStream(object):
   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
@@ -192,6 +340,7 @@ def initLoggerAsDefault(logger, fmt=None, level=None):
   """
   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")
@@ -212,6 +361,7 @@ def initLoggerAsUnittest(logger, fmt=None, level=None):
   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")
@@ -225,6 +375,7 @@ def initLoggerAsUnittest(logger, fmt=None, level=None):
   else:
     logger.setLevel(logger.DEBUG)
 
+
 def setFileHandler(logger, config):
   """
   add file handler to logger to set log files
@@ -240,10 +391,56 @@ def setFileHandler(logger, config):
   |   ~/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()
@@ -253,6 +450,7 @@ def getDefaultLogger():
   LOGI.setLoggerClass(previousClass)
   return res
 
+
 def getUnittestLogger():
   log("getUnittestLogger %s" % _loggerUnittestName)
   # case multithread may be problem as not LOGI._acquireLock()
@@ -261,10 +459,14 @@ def getUnittestLogger():
   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')
@@ -274,6 +476,7 @@ def testLogger_1(logger):
   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()
@@ -291,13 +494,21 @@ def testMain():
   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()
index 917197a9dcc73262008a45aa637b8f1fec85f4c0..c2064efc8f948609e48006e170491b5681de40dc 100644 (file)
@@ -684,7 +684,7 @@ def product_has_script(product_info):
     :param product_info: (Config)
       The configuration specific to the product
     :return: (bool) 
-      True if the product it has a compilation script, else False
+      True if the product has a compilation script, else False
     """
     if "build_source" not in product_info:
         # Native case
@@ -698,7 +698,7 @@ def product_has_env_script(product_info):
     :param product_info: (Config)
       The configuration specific to the product
     :return: (bool) 
-      True if the product it has an environment script, else False
+      True if the product has an environment script, else False
     """
     return "environ" in product_info and "env_script" in product_info.environ
 
index d6f9392bdb4328234b8f098d77269865aae10acf..8e7a0e5b58fd1a1b07bf0e028de2714723db9eff 100644 (file)
@@ -17,7 +17,7 @@
 #  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
@@ -35,6 +35,9 @@ import src.ElementTree as ETREE
 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
@@ -104,7 +107,163 @@ class XmlLogFile(object):
         :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
@@ -158,6 +317,9 @@ class ReadXmlFile(object):
         """
         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.
 
@@ -203,7 +365,6 @@ def find_node_by_attrib(xmlroot, name_node, key, value):
             return node
     return None
     
-
 def write_report(filename, xmlroot, stylesheet):
     """Writes a report file from a XML tree.
     
index 7a2265144a34fbca76d73bd5ab7eafdabf981742..3102386b631d063b18e8c21327bcbfb452ead446 100755 (executable)
@@ -127,7 +127,7 @@ class TestCase(unittest.TestCase):
     # creation d'un handler pour chaque log sur la console
     formatter = LOGI.Formatter('%(levelname)-8s :: %(message)s')
     # stream_handler = LOGI.handlers.StreamHandler() # log outputs in console
-    stream_handler = LOGI.handlers.BufferingHandler(1000) # logoutputs in memory
+    stream_handler = LOGI.handlers.BufferingHandler(1000) # log outputs in memory
     stream_handler.setLevel(LOGI.DEBUG)
     stream_handler.setFormatter(formatter)
     lgr.addHandler(stream_handler)