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$"
42 Class to handle log mechanism.
47 all_in_terminal=False,
48 micro_command = False):
51 :param config pyconf.Config: The global configuration.
52 :param silent_sysstd boolean: if True, do not write anything
55 DBG.write("src.logger.Logger", id(self))
57 self.default_level = 3
58 self.silentSysStd = silent_sysstd
60 # Construct xml log file location for sat prints.
64 hour_command_host = (config.VARS.datehour + "_" +
65 config.VARS.command + "_" +
67 logFileName = prefix + hour_command_host + ".xml"
68 log_dir = src.get_log_path(config)
69 logFilePath = os.path.join(log_dir, logFileName)
70 # Construct txt file location in order to log
71 # the external commands calls (cmake, make, git clone, etc...)
72 txtFileName = prefix + hour_command_host + ".txt"
73 txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
75 aDirLog = os.path.dirname(logFilePath)
76 if not os.path.exists(aDirLog):
77 print("create log dir %s" % aDirLog)
78 src.ensure_path_exists(aDirLog)
79 # sometimes other users make 'sat log' and create hat.xml file...
90 src.ensure_path_exists(os.path.dirname(txtFilePath))
92 # The path of the log files (one for sat traces, and the other for
93 # the system commands traces)
94 self.logFileName = logFileName
95 self.logFilePath = logFilePath
96 self.txtFileName = txtFileName
97 self.txtFilePath = txtFilePath
99 # The list of all log files corresponding to the current command and
100 # the commands called by the current command
101 self.l_logFiles = [logFilePath, txtFilePath]
103 # Initialize xml instance and put first fields
104 # like beginTime, user, command, etc...
105 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
106 attrib = {"application" : config.VARS.application})
107 self.put_initial_xml_fields()
108 # Initialize the txt file for reading
110 self.logTxtFile = open(str(self.txtFilePath), 'w')
112 #msg1 = _("WARNING! Trying to write to a file that"
113 # " is not accessible:")
114 #msg2 = _("The logs won't be written.")
115 #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
116 # src.printcolors.printcLabel(str(self.txtFilePath)),
117 # src.printcolors.printcWarning(msg2) ))
118 self.logTxtFile = tempfile.TemporaryFile()
120 # If the option all_in_terminal was called, all the system commands
121 # are redirected to the terminal
123 self.logTxtFile = sys.__stdout__
125 def put_initial_xml_fields(self):
127 Called at class initialization: Put all fields
128 corresponding to the command context (user, time, ...)
131 self.xmlFile.add_simple_node("Site", attrib={"command" :
132 self.config.VARS.command})
133 # version of salomeTools
134 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
135 self.config.INTERNAL.sat_version})
136 # machine name on which the command has been launched
137 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
138 self.config.VARS.hostname})
139 # Distribution of the machine
140 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
141 self.config.VARS.dist})
142 # The user that have launched the command
143 self.xmlFile.append_node_attrib("Site", attrib={"user" :
144 self.config.VARS.user})
145 # The time when command was launched
146 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
147 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
148 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
150 # The application if any
151 if "APPLICATION" in self.config:
152 self.xmlFile.append_node_attrib("Site",
153 attrib={"application" : self.config.VARS.application})
154 # The initialization of the trace node
155 self.xmlFile.add_simple_node("Log",text="")
156 # The system commands logs
157 self.xmlFile.add_simple_node("OutLog",
158 text=os.path.join("OUT", self.txtFileName))
159 # The initialization of the node where
160 # to put the links to the other sat commands that can be called by any
162 self.xmlFile.add_simple_node("Links")
168 full_launched_command):
169 """Add a link to another log file.
171 :param log_file_name str: The file name of the link.
172 :param command_name str: The name of the command linked.
173 :param command_res str: The result of the command linked. "0" or "1"
174 :parma full_launched_command str: The full lanch command
177 xmlLinks = self.xmlFile.xmlroot.find("Links")
178 flc = src.xmlManager.escapeSequence(full_launched_command)
179 att = {"command" : command_name, "passed" : command_res, "launchedCommand" : flc}
180 src.xmlManager.add_simple_node(xmlLinks, "link", text = log_file_name, attrib = att)
182 def write(self, message, level=None, screenOnly=False):
184 function used in the commands
185 to print in the terminal and the log file.
187 :param message str: The message to print.
188 :param level int: The output level corresponding
189 to the message 0 < level < 6.
190 :param screenOnly boolean: if True, do not write in log file.
192 # avoid traces if unittest
193 if isCurrentLoggerUnittest():
194 # print("doing unittest")
195 sendMessageToCurrentLogger(message, level)
198 # do not write message starting with \r to log file
199 if not message.startswith("\r") and not screenOnly:
200 self.xmlFile.append_node_text("Log",
201 printcolors.cleancolor(message))
203 # get user or option output level
204 current_output_verbose_level = self.config.USER.output_verbose_level
205 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
206 # clean the message color if the terminal is redirected by user
207 # ex: sat compile appli > log.txt
208 message = printcolors.cleancolor(message)
210 # Print message regarding the output level value
212 if level <= current_output_verbose_level and not self.silentSysStd:
213 sys.stdout.write(message)
215 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
216 sys.stdout.write(message)
219 def error(self, message, prefix="ERROR: "):
222 :param message str: The message to print.
224 # Print in the log file
225 self.xmlFile.append_node_text("traces", prefix + message)
227 # Print in the terminal and clean colors if the terminal
228 # is redirected by user
229 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
230 sys.stderr.write(printcolors.printcError(prefix + message + "\n"))
232 sys.stderr.write(prefix + message + "\n")
234 def step(self, message):
235 """Print an step message.
237 :param message str: The message to print.
239 self.write('STEP: ' + message, level=4)
241 def trace(self, message):
242 """Print an trace message.
244 :param message str: The message to print.
246 self.write('TRACE: ' + message, level=5)
248 def debug(self, message):
249 """Print an debug message.
251 :param message str: The message to print.
253 self.write('DEBUG: ' + message, level=6)
255 def warning(self, message):
256 """Print an warning message.
258 :param message str: The message to print.
260 self.error(message, prefix="WARNING: ")
262 def critical(self, message):
263 """Print an critical message.
265 :param message str: The message to print.
267 self.error(message, prefix="CRITICAL: ")
274 self.logTxtFile.flush()
276 def end_write(self, attribute):
278 Called just after command end: Put all fields
279 corresponding to the command end context (time).
280 Write the log xml file on the hard drive.
281 And display the command to launch to get the log
283 :param attribute dict: the attribute to add to the node "Site".
285 # Get current time (end of command) and format it
286 dt = datetime.datetime.now()
287 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
288 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
291 total_time = timedelta_total_seconds(delta)
292 hours = int(total_time / 3600)
293 minutes = int((total_time - hours*3600) / 60)
294 seconds = total_time - hours*3600 - minutes*60
295 # Add the fields corresponding to the end time
296 # and the total time of command
297 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
298 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
299 self.xmlFile.append_node_attrib("Site",
300 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
302 # Add the attribute passed to the method
303 self.xmlFile.append_node_attrib("Site", attrib=attribute)
305 # Call the method to write the xml file on the hard drive
306 self.xmlFile.write_tree(stylesheet = "command.xsl")
308 # so unconditionnaly copy stylesheet file(s)
309 xslDir = os.path.join(self.config.VARS.srcDir, 'xsl')
310 xslCommand = "command.xsl"
311 # xslHat = "hat.xsl" # have to be completed (one time at end)
313 imgLogo = "LOGO-SAT.png"
314 files_to_copy = [xslCommand, xsltest, imgLogo]
316 logDir = src.get_log_path(self.config)
317 # copy the stylesheets in the log directory as soon as possible here
318 # because referenced in self.xmlFile.write_tree above
319 # OP We use copy instead of copy2 to update the creation date
320 # So we can clean the LOGS directories easily
321 for f in files_to_copy:
322 f_init = os.path.join(xslDir, f)
323 f_target = os.path.join(logDir, f)
324 if not os.path.isfile(f_target): # do not overrride
325 shutil.copy(f_init, logDir)
327 # Dump the config in a pyconf file in the log directory
328 dumpedPyconfFileName = (self.config.VARS.datehour
330 + self.config.VARS.command
332 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
334 f = open(dumpedPyconfFilePath, 'w')
335 self.config.__save__(f)
340 def date_to_datetime(date):
342 From a string date in format YYYYMMDD_HHMMSS
343 returns list year, mon, day, hour, minutes, seconds
345 :param date str: The date in format YYYYMMDD_HHMMSS
346 :return: the same date and time in separate variables.
347 :rtype: (str,str,str,str,str,str)
355 return Y, m, dd, H, M, S
357 def timedelta_total_seconds(timedelta):
359 Replace total_seconds from datetime module
360 in order to be compatible with old python versions
362 :param timedelta datetime.timedelta: The delta between two dates
363 :return: The number of seconds corresponding to timedelta.
367 timedelta.microseconds + 0.0 +
368 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
370 def show_command_log(logFilePath, cmd, application, notShownCommands):
372 Used in updateHatXml.
373 Determine if the log xml file logFilePath
374 has to be shown or not in the hat log.
376 :param logFilePath str: the path to the command xml log file
377 :param cmd str: the command of the log file
378 :param application str: the application passed as parameter
379 to the salomeTools command
380 :param notShownCommands list: the list of commands
381 that are not shown by default
383 :return: True if cmd is not in notShownCommands and the application
384 in the log file corresponds to application
387 # When the command is not in notShownCommands, no need to go further :
389 if cmd in notShownCommands:
390 return False, None, None
392 # Get the application of the log file
394 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
395 except Exception as e:
396 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
397 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
398 return False, None, None
400 if 'application' in logFileXml.xmlroot.keys():
401 appliLog = logFileXml.xmlroot.get('application')
402 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
403 # if it corresponds, then the log has to be shown
404 if appliLog == application:
405 return True, appliLog, launched_cmd
406 elif application != 'None':
407 return False, appliLog, launched_cmd
409 return True, appliLog, launched_cmd
411 if application == 'None':
412 return True, None, None
414 return False, None, None
416 def list_log_file(dirPath, expression):
417 """Find all files corresponding to expression in dirPath
419 :param dirPath str: the directory where to search the files
420 :param expression str: the regular expression of files to find
421 :return: the list of files path and informations about it
425 for fileName in os.listdir(dirPath):
426 # YYYYMMDD_HHMMSS_namecmd.xml
428 oExpr = re.compile(sExpr)
429 if oExpr.search(fileName):
431 if fileName.startswith("micro_"):
432 file_name = fileName[len("micro_"):]
433 # get date and hour and format it
434 date_hour_cmd_host = file_name.split('_')
435 date_not_formated = date_hour_cmd_host[0]
436 date = "%s/%s/%s" % (date_not_formated[6:8],
437 date_not_formated[4:6],
438 date_not_formated[0:4])
439 hour_not_formated = date_hour_cmd_host[1]
440 hour = "%s:%s:%s" % (hour_not_formated[0:2],
441 hour_not_formated[2:4],
442 hour_not_formated[4:6])
443 if len(date_hour_cmd_host) < 4:
444 cmd = date_hour_cmd_host[2][:-len('.xml')]
447 cmd = date_hour_cmd_host[2]
448 host = date_hour_cmd_host[3][:-len('.xml')]
449 lRes.append((os.path.join(dirPath, fileName),
458 def update_hat_xml(logDir, application=None, notShownCommands = []):
460 Create the xml file in logDir that contain all the xml file
461 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
463 :param logDir str: the directory to parse
464 :param application str: the name of the application if there is any
466 # Create an instance of XmlLogFile class to create hat.xml file
467 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
468 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath, "LOGlist", {"application" : application})
469 # parse the log directory to find all the command logs,
470 # then add it to the xml file
471 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
472 for filePath, __, date, __, hour, cmd, __ in lLogFile:
473 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
474 application, notShownCommands)
475 #if cmd not in notShownCommands:
477 # add a node to the hat.xml file
478 xmlHat.add_simple_node("LogCommand",
479 text=os.path.basename(filePath),
480 attrib = {"date" : date,
483 "application" : cmdAppli,
484 "full_command" : full_cmd})
486 # Write the file on the hard drive
487 xmlHat.write_tree('hat.xsl')
488 # Sometimes other users will make 'sat log' and update this file
489 os.chmod(xmlHatFilePath,
500 # prepare skip to logging logger sat5.1
501 # suppose only one logger in sat5.1
504 def getCurrentLogger():
505 """get current logging logger, set as DefaultLogger if not set yet"""
506 if len(_currentLogger) == 0:
507 import src.loggingSimple as LOGSI
508 logger = LOGSI.getDefaultLogger()
509 _currentLogger.append(logger)
510 logger.warning("set by default current logger as %s" % logger.name)
511 return _currentLogger[0]
513 def getDefaultLogger():
514 """get simple logging logger DefaultLogger, set it as current"""
515 import src.loggingSimple as LOGSI
516 logger = LOGSI.getDefaultLogger()
517 setCurrentLogger(logger) # set it as current
520 def getUnittestLogger():
521 """get simple logging logger UnittestLogger, set it as current"""
522 import src.loggingSimple as LOGSI
523 logger = LOGSI.getUnittestLogger()
524 setCurrentLogger(logger) # set it as current
527 def setCurrentLogger(logger):
528 """temporary send all in stdout as simple logging logger"""
529 if len(_currentLogger) == 0:
530 _currentLogger.append(logger)
531 logger.debug("set current logger as %s" % logger.name)
533 if _currentLogger[0].name != logger.name:
534 # logger.debug("quit current logger as default %s" % _currentLogger[0].name)
535 _currentLogger[0] = logger
536 logger.warning("change current logger as %s" % logger.name)
537 return _currentLogger[0]
539 def isCurrentLoggerUnittest():
540 logger = getCurrentLogger()
541 if "Unittest" in logger.name:
545 DBG.write("isCurrentLoggerUnittest %s" % logger.name, res)
548 def sendMessageToCurrentLogger(message, level):
550 assume relay from obsolescent
551 logger.write(msg, 1/2/3...) to future
552 logging.critical/warning/info...(msg) (as logging package tips)
554 logger = getCurrentLogger()
560 logger.critical(message)
563 logger.warning(message)
572 logger.trace(message)
575 logger.debug(message)
577 msg = "What is this level: '%s' for message:\n%s" % (level, message)