3 # Copyright (C) 2010-2012 CEA/DEN
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 Implements the classes and method relative to the logging
35 import src.debug as DBG
37 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
38 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
40 verbose = True # cvw TODO
44 Class to handle log mechanism.
49 all_in_terminal=False,
50 micro_command = False):
53 :param config pyconf.Config: The global configuration.
54 :param silent_sysstd boolean: if True, do not write anything
57 DBG.write("src.logger.Logger", id(self))
59 self.default_level = 3
60 self.silentSysStd = silent_sysstd
62 # Construct xml log file location for sat prints.
66 hour_command_host = (config.VARS.datehour + "_" +
67 config.VARS.command + "_" +
69 logFileName = prefix + hour_command_host + ".xml"
70 log_dir = src.get_log_path(config)
71 logFilePath = os.path.join(log_dir, logFileName)
72 # Construct txt file location in order to log
73 # the external commands calls (cmake, make, git clone, etc...)
74 txtFileName = prefix + hour_command_host + ".txt"
75 txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
77 aDirLog = os.path.dirname(logFilePath)
78 if not os.path.exists(aDirLog):
79 print("create log dir %s" % aDirLog)
80 src.ensure_path_exists(aDirLog)
81 # sometimes other users make 'sat log' and create hat.xml file...
92 src.ensure_path_exists(os.path.dirname(txtFilePath))
94 # The path of the log files (one for sat traces, and the other for
95 # the system commands traces)
96 self.logFileName = logFileName
97 self.logFilePath = logFilePath
98 self.txtFileName = txtFileName
99 self.txtFilePath = txtFilePath
101 # The list of all log files corresponding to the current command and
102 # the commands called by the current command
103 self.l_logFiles = [logFilePath, txtFilePath]
105 # Initialize xml instance and put first fields
106 # like beginTime, user, command, etc...
107 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
108 attrib = {"application" : config.VARS.application})
109 self.put_initial_xml_fields()
110 # Initialize the txt file for reading
112 self.logTxtFile = open(str(self.txtFilePath), 'w')
114 #msg1 = _("WARNING! Trying to write to a file that"
115 # " is not accessible:")
116 #msg2 = _("The logs won't be written.")
117 #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
118 # src.printcolors.printcLabel(str(self.txtFilePath)),
119 # src.printcolors.printcWarning(msg2) ))
120 self.logTxtFile = tempfile.TemporaryFile()
122 # If the option all_in_terminal was called, all the system commands
123 # are redirected to the terminal
125 self.logTxtFile = sys.__stdout__
127 def put_initial_xml_fields(self):
129 Called at class initialization: Put all fields
130 corresponding to the command context (user, time, ...)
133 self.xmlFile.add_simple_node("Site", attrib={"command" :
134 self.config.VARS.command})
135 # version of salomeTools
136 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
137 self.config.INTERNAL.sat_version})
138 # machine name on which the command has been launched
139 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
140 self.config.VARS.hostname})
141 # Distribution of the machine
142 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
143 self.config.VARS.dist})
144 # The user that have launched the command
145 self.xmlFile.append_node_attrib("Site", attrib={"user" :
146 self.config.VARS.user})
147 # The time when command was launched
148 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
149 date_hour = "%4s/%2s/%2s %2sh%2sm%2ss" % (Y, m, dd, H, M, S)
150 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
152 # The application if any
153 if "APPLICATION" in self.config:
154 self.xmlFile.append_node_attrib("Site",
155 attrib={"application" : self.config.VARS.application})
156 # The initialization of the trace node
157 self.xmlFile.add_simple_node("Log",text="")
158 # The system commands logs
159 self.xmlFile.add_simple_node("OutLog",
160 text=os.path.join("OUT", self.txtFileName))
161 # The initialization of the node where
162 # to put the links to the other sat commands that can be called by any
164 self.xmlFile.add_simple_node("Links")
170 full_launched_command):
171 """Add a link to another log file.
173 :param log_file_name str: The file name of the link.
174 :param command_name str: The name of the command linked.
175 :param command_res str: The result of the command linked. "0" or "1"
176 :parma full_launched_command str: The full lanch command
179 xmlLinks = self.xmlFile.xmlroot.find("Links")
180 flc = src.xmlManager.escapeSequence(full_launched_command)
181 att = {"command" : command_name, "passed" : str(command_res), "launchedCommand" : flc}
182 src.xmlManager.add_simple_node(xmlLinks, "link", text = log_file_name, attrib = att)
184 def write(self, message, level=None, screenOnly=False):
186 function used in the commands
187 to print in the terminal and the log file.
189 :param message str: The message to print.
190 :param level int: The output level corresponding
191 to the message 0 < level < 6.
192 :param screenOnly boolean: if True, do not write in log file.
194 # avoid traces if unittest
195 if isCurrentLoggerUnittest():
196 # print("doing unittest")
197 sendMessageToCurrentLogger(message, level)
200 # do not write message starting with \r to log file
201 if not message.startswith("\r") and not screenOnly:
202 self.xmlFile.append_node_text("Log",
203 printcolors.cleancolor(message))
205 # get user or option output level
206 current_output_verbose_level = self.config.USER.output_verbose_level
207 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
208 # clean the message color if the terminal is redirected by user
209 # ex: sat compile appli > log.txt
210 message = printcolors.cleancolor(message)
212 # Print message regarding the output level value
214 if level <= current_output_verbose_level and not self.silentSysStd:
215 sys.stdout.write(message)
217 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
218 sys.stdout.write(message)
221 def error(self, message, prefix="ERROR: "):
224 :param message str: The message to print.
226 # Print in the log file
227 self.xmlFile.append_node_text("traces", prefix + message)
229 # Print in the terminal and clean colors if the terminal
230 # is redirected by user
231 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
232 sys.stderr.write(printcolors.printcError(prefix + message + "\n"))
234 sys.stderr.write(prefix + message + "\n")
236 def step(self, message):
237 """Print an step message.
239 :param message str: The message to print.
241 self.write('STEP: ' + message, level=4)
243 def trace(self, message):
244 """Print an trace message.
246 :param message str: The message to print.
248 self.write('TRACE: ' + message, level=5)
250 def debug(self, message):
251 """Print an debug message.
253 :param message str: The message to print.
255 self.write('DEBUG: ' + message, level=6)
257 def warning(self, message):
258 """Print an warning message.
260 :param message str: The message to print.
262 self.error(message, prefix="WARNING: ")
264 def critical(self, message):
265 """Print an critical message.
267 :param message str: The message to print.
269 self.error(message, prefix="CRITICAL: ")
276 self.logTxtFile.flush()
278 def end_write(self, attribute):
280 Called just after command end: Put all fields
281 corresponding to the command end context (time).
282 Write the log xml file on the hard drive.
283 And display the command to launch to get the log
285 :param attribute dict: the attribute to add to the node "Site".
287 # Get current time (end of command) and format it
288 dt = datetime.datetime.now()
289 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
290 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
293 total_time = timedelta_total_seconds(delta)
294 hours = int(total_time / 3600)
295 minutes = int((total_time - hours*3600) / 60)
296 seconds = total_time - hours*3600 - minutes*60
297 # Add the fields corresponding to the end time
298 # and the total time of command
299 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
300 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
301 self.xmlFile.append_node_attrib("Site",
302 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
304 # Add the attribute passed to the method
305 self.xmlFile.append_node_attrib("Site", attrib=attribute)
307 # Call the method to write the xml file on the hard drive
308 self.xmlFile.write_tree(stylesheet = "command.xsl")
310 # so unconditionnaly copy stylesheet file(s)
311 xslDir = os.path.join(self.config.VARS.srcDir, 'xsl')
312 xslCommand = "command.xsl"
313 # xslHat = "hat.xsl" # have to be completed (one time at end)
315 imgLogo = "LOGO-SAT.png"
316 files_to_copy = [xslCommand, xsltest, imgLogo]
318 logDir = src.get_log_path(self.config)
319 # copy the stylesheets in the log directory as soon as possible here
320 # because referenced in self.xmlFile.write_tree above
321 # OP We use copy instead of copy2 to update the creation date
322 # So we can clean the LOGS directories easily
323 for f in files_to_copy:
324 f_init = os.path.join(xslDir, f)
325 f_target = os.path.join(logDir, f)
326 if not os.path.isfile(f_target): # do not overrride
327 shutil.copy(f_init, logDir)
329 # Dump the config in a pyconf file in the log directory
330 dumpedPyconfFileName = (self.config.VARS.datehour
332 + self.config.VARS.command
334 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
336 f = open(dumpedPyconfFilePath, 'w')
337 self.config.__save__(f)
342 def date_to_datetime(date):
344 From a string date in format YYYYMMDD_HHMMSS
345 returns list year, mon, day, hour, minutes, seconds
347 :param date str: The date in format YYYYMMDD_HHMMSS
348 :return: the same date and time in separate variables.
349 :rtype: (str,str,str,str,str,str)
357 return Y, m, dd, H, M, S
359 def timedelta_total_seconds(timedelta):
361 Replace total_seconds from datetime module
362 in order to be compatible with old python versions
364 :param timedelta datetime.timedelta: The delta between two dates
365 :return: The number of seconds corresponding to timedelta.
369 timedelta.microseconds + 0.0 +
370 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
372 def show_command_log(logFilePath, cmd, application, notShownCommands):
374 Used in updateHatXml.
375 Determine if the log xml file logFilePath
376 has to be shown or not in the hat log.
378 :param logFilePath str: the path to the command xml log file
379 :param cmd str: the command of the log file
380 :param application str: the application passed as parameter
381 to the salomeTools command
382 :param notShownCommands list: the list of commands
383 that are not shown by default
385 :return: True if cmd is not in notShownCommands and the application
386 in the log file corresponds to application
389 # When the command is not in notShownCommands, no need to go further :
391 if cmd in notShownCommands:
392 return False, None, None
394 # Get the application of the log file
396 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
397 except Exception as e:
398 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
399 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
400 return False, None, None
403 if 'application' in logFileXml.xmlroot.keys():
404 appliLog = logFileXml.xmlroot.get('application')
405 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
406 # if it corresponds, then the log has to be shown
407 if appliLog == application:
408 return True, appliLog, launched_cmd
409 elif application != 'None':
410 return False, appliLog, launched_cmd
412 return True, appliLog, launched_cmd
413 except Exception as e:
414 msg = _("WARNING: the log file %s cannot be parsed:" % logFilePath)
415 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
416 return False, None, None
418 if application == 'None':
419 return True, None, None
421 return False, None, None
423 def list_log_file(dirPath, expression):
424 """Find all files corresponding to expression in dirPath
426 :param dirPath str: the directory where to search the files
427 :param expression str: the regular expression of files to find
428 :return: the list of files path and informations about it
432 for fileName in os.listdir(dirPath):
433 # YYYYMMDD_HHMMSS_namecmd.xml
435 oExpr = re.compile(sExpr)
436 if oExpr.search(fileName):
438 if fileName.startswith("micro_"):
439 file_name = fileName[len("micro_"):]
440 # get date and hour and format it
441 date_hour_cmd_host = file_name.split('_')
442 date_not_formated = date_hour_cmd_host[0]
443 date = "%s/%s/%s" % (date_not_formated[6:8],
444 date_not_formated[4:6],
445 date_not_formated[0:4])
446 hour_not_formated = date_hour_cmd_host[1]
447 hour = "%s:%s:%s" % (hour_not_formated[0:2],
448 hour_not_formated[2:4],
449 hour_not_formated[4:6])
450 if len(date_hour_cmd_host) < 4:
451 cmd = date_hour_cmd_host[2][:-len('.xml')]
454 cmd = date_hour_cmd_host[2]
455 host = date_hour_cmd_host[3][:-len('.xml')]
456 lRes.append((os.path.join(dirPath, fileName),
465 def update_hat_xml(logDir, application=None, notShownCommands = []):
467 Create the xml file in logDir that contain all the xml file
468 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
470 :param logDir str: the directory to parse
471 :param application str: the name of the application if there is any
473 # Create an instance of XmlLogFile class to create hat.xml file
474 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
475 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath, "LOGlist", {"application" : application})
476 # parse the log directory to find all the command logs,
477 # then add it to the xml file
478 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
479 for filePath, __, date, __, hour, cmd, __ in lLogFile:
480 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
481 application, notShownCommands)
482 #if cmd not in notShownCommands:
484 # add a node to the hat.xml file
485 xmlHat.add_simple_node("LogCommand",
486 text=os.path.basename(filePath),
487 attrib = {"date" : date,
490 "application" : cmdAppli,
491 "full_command" : full_cmd})
493 # Write the file on the hard drive
494 xmlHat.write_tree('hat.xsl')
495 # Sometimes other users will make 'sat log' and update this file
496 os.chmod(xmlHatFilePath,
507 # prepare skip to logging logger sat5.1
508 # suppose only one logger in sat5.1
511 def getCurrentLogger():
512 """get current logging logger, set as DefaultLogger if not set yet"""
513 if len(_currentLogger) == 0:
514 import src.loggingSimple as LOGSI
515 logger = LOGSI.getDefaultLogger()
516 _currentLogger.append(logger)
517 logger.warning("set by default current logger as %s" % logger.name)
518 return _currentLogger[0]
520 def getDefaultLogger():
521 """get simple logging logger DefaultLogger, set it as current"""
522 import src.loggingSimple as LOGSI
523 logger = LOGSI.getDefaultLogger()
524 setCurrentLogger(logger) # set it as current
527 def getUnittestLogger():
528 """get simple logging logger UnittestLogger, set it as current"""
529 import src.loggingSimple as LOGSI
530 logger = LOGSI.getUnittestLogger()
531 setCurrentLogger(logger) # set it as current
534 def setCurrentLogger(logger):
535 """temporary send all in stdout as simple logging logger"""
536 if len(_currentLogger) == 0:
537 _currentLogger.append(logger)
538 logger.debug("set current logger as %s" % logger.name)
540 if _currentLogger[0].name != logger.name:
541 # logger.debug("quit current logger as default %s" % _currentLogger[0].name)
542 _currentLogger[0] = logger
543 logger.warning("change current logger as %s" % logger.name)
544 return _currentLogger[0]
546 def isCurrentLoggerUnittest():
547 logger = getCurrentLogger()
548 if "Unittest" in logger.name:
552 #DBG.write("isCurrentLoggerUnittest %s" % logger.name, res)
555 def sendMessageToCurrentLogger(message, level):
557 assume relay from obsolescent
558 logger.write(msg, 1/2/3...) to future
559 logging.critical/warning/info...(msg) (as logging package tips)
561 logger = getCurrentLogger()
567 logger.critical(message)
570 logger.warning(message)
579 logger.trace(message)
582 logger.debug(message)
584 msg = "What is this level: '%s' for message:\n%s" % (level, message)