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
34 import src.debug as DBG
36 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
37 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
41 Class to handle log mechanism.
46 all_in_terminal=False,
47 micro_command = False):
50 :param config pyconf.Config: The global configuration.
51 :param silent_sysstd boolean: if True, do not write anything
54 DBG.write("src.logger.Logger", id(self))
56 self.default_level = 3
57 self.silentSysStd = silent_sysstd
59 # Construct xml log file location for sat prints.
63 hour_command_host = (config.VARS.datehour + "_" +
64 config.VARS.command + "_" +
66 logFileName = prefix + hour_command_host + ".xml"
67 log_dir = src.get_log_path(config)
68 logFilePath = os.path.join(log_dir, logFileName)
69 # Construct txt file location in order to log
70 # the external commands calls (cmake, make, git clone, etc...)
71 txtFileName = prefix + hour_command_host + ".txt"
72 txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
74 src.ensure_path_exists(os.path.dirname(logFilePath))
75 src.ensure_path_exists(os.path.dirname(txtFilePath))
77 # The path of the log files (one for sat traces, and the other for
78 # the system commands traces)
79 self.logFileName = logFileName
80 self.logFilePath = logFilePath
81 self.txtFileName = txtFileName
82 self.txtFilePath = txtFilePath
84 # The list of all log files corresponding to the current command and
85 # the commands called by the current command
86 self.l_logFiles = [logFilePath, txtFilePath]
88 # Initialize xml instance and put first fields
89 # like beginTime, user, command, etc...
90 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
91 attrib = {"application" : config.VARS.application})
92 self.put_initial_xml_fields()
93 # Initialize the txt file for reading
95 self.logTxtFile = open(str(self.txtFilePath), 'w')
97 #msg1 = _("WARNING! Trying to write to a file that"
98 # " is not accessible:")
99 #msg2 = _("The logs won't be written.")
100 #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
101 # src.printcolors.printcLabel(str(self.txtFilePath)),
102 # src.printcolors.printcWarning(msg2) ))
103 self.logTxtFile = tempfile.TemporaryFile()
105 # If the option all_in_terminal was called, all the system commands
106 # are redirected to the terminal
108 self.logTxtFile = sys.__stdout__
110 def put_initial_xml_fields(self):
112 Called at class initialization: Put all fields
113 corresponding to the command context (user, time, ...)
116 self.xmlFile.add_simple_node("Site", attrib={"command" :
117 self.config.VARS.command})
118 # version of salomeTools
119 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
120 self.config.INTERNAL.sat_version})
121 # machine name on which the command has been launched
122 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
123 self.config.VARS.hostname})
124 # Distribution of the machine
125 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
126 self.config.VARS.dist})
127 # The user that have launched the command
128 self.xmlFile.append_node_attrib("Site", attrib={"user" :
129 self.config.VARS.user})
130 # The time when command was launched
131 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
132 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
133 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
135 # The application if any
136 if "APPLICATION" in self.config:
137 self.xmlFile.append_node_attrib("Site",
138 attrib={"application" : self.config.VARS.application})
139 # The initialization of the trace node
140 self.xmlFile.add_simple_node("Log",text="")
141 # The system commands logs
142 self.xmlFile.add_simple_node("OutLog",
143 text=os.path.join("OUT", self.txtFileName))
144 # The initialization of the node where
145 # to put the links to the other sat commands that can be called by any
147 self.xmlFile.add_simple_node("Links")
153 full_launched_command):
154 """Add a link to another log file.
156 :param log_file_name str: The file name of the link.
157 :param command_name str: The name of the command linked.
158 :param command_res str: The result of the command linked. "0" or "1"
159 :parma full_launched_command str: The full lanch command
162 xmlLinks = self.xmlFile.xmlroot.find("Links")
163 flc = src.xmlManager.escapeSequence(full_launched_command)
164 att = {"command" : command_name, "passed" : command_res, "launchedCommand" : flc}
165 src.xmlManager.add_simple_node(xmlLinks, "link", text = log_file_name, attrib = att)
167 def write(self, message, level=None, screenOnly=False):
169 function used in the commands
170 to print in the terminal and the log file.
172 :param message str: The message to print.
173 :param level int: The output level corresponding
174 to the message 0 < level < 6.
175 :param screenOnly boolean: if True, do not write in log file.
177 # avoid traces if unittest
178 if isCurrentLoggerUnittest():
179 # print("doing unittest")
180 sendMessageToCurrentLogger(message, level)
183 # do not write message starting with \r to log file
184 if not message.startswith("\r") and not screenOnly:
185 self.xmlFile.append_node_text("Log",
186 printcolors.cleancolor(message))
188 # get user or option output level
189 current_output_verbose_level = self.config.USER.output_verbose_level
190 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
191 # clean the message color if the terminal is redirected by user
192 # ex: sat compile appli > log.txt
193 message = printcolors.cleancolor(message)
195 # Print message regarding the output level value
197 if level <= current_output_verbose_level and not self.silentSysStd:
198 sys.stdout.write(message)
200 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
201 sys.stdout.write(message)
204 def error(self, message, prefix="ERROR: "):
207 :param message str: The message to print.
209 # Print in the log file
210 self.xmlFile.append_node_text("traces", prefix + message)
212 # Print in the terminal and clean colors if the terminal
213 # is redirected by user
214 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
215 sys.stderr.write(printcolors.printcError(prefix + message + "\n"))
217 sys.stderr.write(prefix + message + "\n")
219 def step(self, message):
220 """Print an step message.
222 :param message str: The message to print.
224 self.write('STEP: ' + message, level=4)
226 def trace(self, message):
227 """Print an trace message.
229 :param message str: The message to print.
231 self.write('TRACE: ' + message, level=5)
233 def debug(self, message):
234 """Print an debug message.
236 :param message str: The message to print.
238 self.write('DEBUG: ' + message, level=6)
240 def warning(self, message):
241 """Print an warning message.
243 :param message str: The message to print.
245 self.error(message, prefix="WARNING: ")
247 def critical(self, message):
248 """Print an critical message.
250 :param message str: The message to print.
252 self.error(message, prefix="CRITICAL: ")
259 self.logTxtFile.flush()
261 def end_write(self, attribute):
263 Called just after command end: Put all fields
264 corresponding to the command end context (time).
265 Write the log xml file on the hard drive.
266 And display the command to launch to get the log
268 :param attribute dict: the attribute to add to the node "Site".
270 # Get current time (end of command) and format it
271 dt = datetime.datetime.now()
272 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
273 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
276 total_time = timedelta_total_seconds(delta)
277 hours = int(total_time / 3600)
278 minutes = int((total_time - hours*3600) / 60)
279 seconds = total_time - hours*3600 - minutes*60
280 # Add the fields corresponding to the end time
281 # and the total time of command
282 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
283 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
284 self.xmlFile.append_node_attrib("Site",
285 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
287 # Add the attribute passed to the method
288 self.xmlFile.append_node_attrib("Site", attrib=attribute)
290 # Call the method to write the xml file on the hard drive
291 self.xmlFile.write_tree(stylesheet = "command.xsl")
293 # so unconditionnaly copy stylesheet file(s)
294 xslDir = os.path.join(self.config.VARS.srcDir, 'xsl')
295 xslCommand = "command.xsl"
296 # xslHat = "hat.xsl" # have to be completed (one time at end)
298 imgLogo = "LOGO-SAT.png"
299 files_to_copy = [xslCommand, xsltest, imgLogo]
301 logDir = src.get_log_path(self.config)
302 # copy the stylesheets in the log directory as soon as possible here
303 # because referenced in self.xmlFile.write_tree above
304 # OP We use copy instead of copy2 to update the creation date
305 # So we can clean the LOGS directories easily
306 for f in files_to_copy:
307 f_init = os.path.join(xslDir, f)
308 f_target = os.path.join(logDir, f)
309 if not os.path.isfile(f_target): # do not overrride
310 shutil.copy(f_init, logDir)
312 # Dump the config in a pyconf file in the log directory
313 dumpedPyconfFileName = (self.config.VARS.datehour
315 + self.config.VARS.command
317 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
319 f = open(dumpedPyconfFilePath, 'w')
320 self.config.__save__(f)
325 def date_to_datetime(date):
327 From a string date in format YYYYMMDD_HHMMSS
328 returns list year, mon, day, hour, minutes, seconds
330 :param date str: The date in format YYYYMMDD_HHMMSS
331 :return: the same date and time in separate variables.
332 :rtype: (str,str,str,str,str,str)
340 return Y, m, dd, H, M, S
342 def timedelta_total_seconds(timedelta):
344 Replace total_seconds from datetime module
345 in order to be compatible with old python versions
347 :param timedelta datetime.timedelta: The delta between two dates
348 :return: The number of seconds corresponding to timedelta.
352 timedelta.microseconds + 0.0 +
353 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
355 def show_command_log(logFilePath, cmd, application, notShownCommands):
357 Used in updateHatXml.
358 Determine if the log xml file logFilePath
359 has to be shown or not in the hat log.
361 :param logFilePath str: the path to the command xml log file
362 :param cmd str: the command of the log file
363 :param application str: the application passed as parameter
364 to the salomeTools command
365 :param notShownCommands list: the list of commands
366 that are not shown by default
368 :return: True if cmd is not in notShownCommands and the application
369 in the log file corresponds to application
372 # When the command is not in notShownCommands, no need to go further :
374 if cmd in notShownCommands:
375 return False, None, None
377 # Get the application of the log file
379 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
380 except Exception as e:
381 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
382 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
383 return False, None, None
385 if 'application' in logFileXml.xmlroot.keys():
386 appliLog = logFileXml.xmlroot.get('application')
387 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
388 # if it corresponds, then the log has to be shown
389 if appliLog == application:
390 return True, appliLog, launched_cmd
391 elif application != 'None':
392 return False, appliLog, launched_cmd
394 return True, appliLog, launched_cmd
396 if application == 'None':
397 return True, None, None
399 return False, None, None
401 def list_log_file(dirPath, expression):
402 """Find all files corresponding to expression in dirPath
404 :param dirPath str: the directory where to search the files
405 :param expression str: the regular expression of files to find
406 :return: the list of files path and informations about it
410 for fileName in os.listdir(dirPath):
411 # YYYYMMDD_HHMMSS_namecmd.xml
413 oExpr = re.compile(sExpr)
414 if oExpr.search(fileName):
416 if fileName.startswith("micro_"):
417 file_name = fileName[len("micro_"):]
418 # get date and hour and format it
419 date_hour_cmd_host = file_name.split('_')
420 date_not_formated = date_hour_cmd_host[0]
421 date = "%s/%s/%s" % (date_not_formated[6:8],
422 date_not_formated[4:6],
423 date_not_formated[0:4])
424 hour_not_formated = date_hour_cmd_host[1]
425 hour = "%s:%s:%s" % (hour_not_formated[0:2],
426 hour_not_formated[2:4],
427 hour_not_formated[4:6])
428 if len(date_hour_cmd_host) < 4:
429 cmd = date_hour_cmd_host[2][:-len('.xml')]
432 cmd = date_hour_cmd_host[2]
433 host = date_hour_cmd_host[3][:-len('.xml')]
434 lRes.append((os.path.join(dirPath, fileName),
443 def update_hat_xml(logDir, application=None, notShownCommands = []):
445 Create the xml file in logDir that contain all the xml file
446 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
448 :param logDir str: the directory to parse
449 :param application str: the name of the application if there is any
451 # Create an instance of XmlLogFile class to create hat.xml file
452 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
453 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
454 "LOGlist", {"application" : application})
455 # parse the log directory to find all the command logs,
456 # then add it to the xml file
457 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
458 for filePath, __, date, __, hour, cmd, __ in lLogFile:
459 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
460 application, notShownCommands)
461 #if cmd not in notShownCommands:
463 # add a node to the hat.xml file
464 xmlHat.add_simple_node("LogCommand",
465 text=os.path.basename(filePath),
466 attrib = {"date" : date,
469 "application" : cmdAppli,
470 "full_command" : full_cmd})
472 # Write the file on the hard drive
473 xmlHat.write_tree('hat.xsl')
477 # prepare skip to logging logger sat5.1
478 # suppose only one logger in sat5.1
481 def getCurrentLogger():
482 """get current logging logger, set as DefaultLogger if not set yet"""
483 if len(_currentLogger) == 0:
484 import src.loggingSimple as LOGSI
485 logger = LOGSI.getDefaultLogger()
486 _currentLogger.append(logger)
487 logger.warning("set by default current logger as %s" % logger.name)
488 return _currentLogger[0]
490 def getDefaultLogger():
491 """get simple logging logger DefaultLogger, set it as current"""
492 import src.loggingSimple as LOGSI
493 logger = LOGSI.getDefaultLogger()
494 setCurrentLogger(logger) # set it as current
497 def getUnittestLogger():
498 """get simple logging logger UnittestLogger, set it as current"""
499 import src.loggingSimple as LOGSI
500 logger = LOGSI.getUnittestLogger()
501 setCurrentLogger(logger) # set it as current
504 def setCurrentLogger(logger):
505 """temporary send all in stdout as simple logging logger"""
506 if len(_currentLogger) == 0:
507 _currentLogger.append(logger)
508 logger.debug("set current logger as %s" % logger.name)
510 if _currentLogger[0].name != logger.name:
511 # logger.debug("quit current logger as default %s" % _currentLogger[0].name)
512 _currentLogger[0] = logger
513 logger.warning("change current logger as %s" % logger.name)
514 return _currentLogger[0]
516 def isCurrentLoggerUnittest():
517 logger = getCurrentLogger()
518 if "Unittest" in logger.name:
522 DBG.write("isCurrentLoggerUnittest %s" % logger.name, res)
525 def sendMessageToCurrentLogger(message, level):
527 assume relay from obsolescent
528 logger.write(msg, 1/2/3...) to future
529 logging.critical/warning/info...(msg) (as logging package tips)
531 logger = getCurrentLogger()
537 logger.critical(message)
540 logger.warning(message)
549 logger.trace(message)
552 logger.debug(message)
554 msg = "What is this level: '%s' for message:\n%s" % (level, message)