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
33 import src.debug as DBG
35 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
36 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
40 Class to handle log mechanism.
45 all_in_terminal=False,
46 micro_command = False):
49 :param config pyconf.Config: The global configuration.
50 :param silent_sysstd boolean: if True, do not write anything
53 DBG.write("src.logger.Logger", id(self))
55 self.default_level = 3
56 self.silentSysStd = silent_sysstd
58 # Construct xml log file location for sat prints.
62 hour_command_host = (config.VARS.datehour + "_" +
63 config.VARS.command + "_" +
65 logFileName = prefix + hour_command_host + ".xml"
66 log_dir = src.get_log_path(config)
67 logFilePath = os.path.join(log_dir, logFileName)
68 # Construct txt file location in order to log
69 # the external commands calls (cmake, make, git clone, etc...)
70 txtFileName = prefix + hour_command_host + ".txt"
71 txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
73 src.ensure_path_exists(os.path.dirname(logFilePath))
74 src.ensure_path_exists(os.path.dirname(txtFilePath))
76 # The path of the log files (one for sat traces, and the other for
77 # the system commands traces)
78 self.logFileName = logFileName
79 self.logFilePath = logFilePath
80 self.txtFileName = txtFileName
81 self.txtFilePath = txtFilePath
83 # The list of all log files corresponding to the current command and
84 # the commands called by the current command
85 self.l_logFiles = [logFilePath, txtFilePath]
87 # Initialize xml instance and put first fields
88 # like beginTime, user, command, etc...
89 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
90 attrib = {"application" : config.VARS.application})
91 self.put_initial_xml_fields()
92 # Initialize the txt file for reading
94 self.logTxtFile = open(str(self.txtFilePath), 'w')
96 #msg1 = _("WARNING! Trying to write to a file that"
97 # " is not accessible:")
98 #msg2 = _("The logs won't be written.")
99 #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
100 # src.printcolors.printcLabel(str(self.txtFilePath)),
101 # src.printcolors.printcWarning(msg2) ))
102 self.logTxtFile = tempfile.TemporaryFile()
104 # If the option all_in_terminal was called, all the system commands
105 # are redirected to the terminal
107 self.logTxtFile = sys.__stdout__
109 def put_initial_xml_fields(self):
111 Called at class initialization: Put all fields
112 corresponding to the command context (user, time, ...)
115 self.xmlFile.add_simple_node("Site", attrib={"command" :
116 self.config.VARS.command})
117 # version of salomeTools
118 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
119 self.config.INTERNAL.sat_version})
120 # machine name on which the command has been launched
121 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
122 self.config.VARS.hostname})
123 # Distribution of the machine
124 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
125 self.config.VARS.dist})
126 # The user that have launched the command
127 self.xmlFile.append_node_attrib("Site", attrib={"user" :
128 self.config.VARS.user})
129 # The time when command was launched
130 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
131 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
132 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
134 # The application if any
135 if "APPLICATION" in self.config:
136 self.xmlFile.append_node_attrib("Site",
137 attrib={"application" : self.config.VARS.application})
138 # The initialization of the trace node
139 self.xmlFile.add_simple_node("Log",text="")
140 # The system commands logs
141 self.xmlFile.add_simple_node("OutLog",
142 text=os.path.join("OUT", self.txtFileName))
143 # The initialization of the node where
144 # to put the links to the other sat commands that can be called by any
146 self.xmlFile.add_simple_node("Links")
152 full_launched_command):
153 """Add a link to another log file.
155 :param log_file_name str: The file name of the link.
156 :param command_name str: The name of the command linked.
157 :param command_res str: The result of the command linked. "0" or "1"
158 :parma full_launched_command str: The full lanch command
161 xmlLinks = self.xmlFile.xmlroot.find("Links")
162 src.xmlManager.add_simple_node(xmlLinks,
164 text = log_file_name,
165 attrib = {"command" : command_name,
166 "passed" : command_res,
167 "launchedCommand" : full_launched_command})
169 def write(self, message, level=None, screenOnly=False):
171 function used in the commands
172 to print in the terminal and the log file.
174 :param message str: The message to print.
175 :param level int: The output level corresponding
176 to the message 0 < level < 6.
177 :param screenOnly boolean: if True, do not write in log file.
179 # avoid traces if unittest
180 if isCurrentLoggerUnittest():
181 # print("doing unittest")
182 sendMessageToCurrentLogger(message, level)
185 # do not write message starting with \r to log file
186 if not message.startswith("\r") and not screenOnly:
187 self.xmlFile.append_node_text("Log",
188 printcolors.cleancolor(message))
190 # get user or option output level
191 current_output_verbose_level = self.config.USER.output_verbose_level
192 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
193 # clean the message color if the terminal is redirected by user
194 # ex: sat compile appli > log.txt
195 message = printcolors.cleancolor(message)
197 # Print message regarding the output level value
199 if level <= current_output_verbose_level and not self.silentSysStd:
200 sys.stdout.write(message)
202 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
203 sys.stdout.write(message)
206 def error(self, message):
209 :param message str: The message to print.
211 # Print in the log file
212 self.xmlFile.append_node_text("traces", _('ERROR:') + message)
214 # Print in the terminal and clean colors if the terminal
215 # is redirected by user
216 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
217 sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
219 sys.stderr.write(_('ERROR:') + message)
224 self.logTxtFile.flush()
226 def end_write(self, attribute):
228 Called just after command end: Put all fields
229 corresponding to the command end context (time).
230 Write the log xml file on the hard drive.
231 And display the command to launch to get the log
233 :param attribute dict: the attribute to add to the node "Site".
235 # Get current time (end of command) and format it
236 dt = datetime.datetime.now()
237 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
238 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
241 total_time = timedelta_total_seconds(delta)
242 hours = int(total_time / 3600)
243 minutes = int((total_time - hours*3600) / 60)
244 seconds = total_time - hours*3600 - minutes*60
245 # Add the fields corresponding to the end time
246 # and the total time of command
247 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
248 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
249 self.xmlFile.append_node_attrib("Site",
250 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
252 # Add the attribute passed to the method
253 self.xmlFile.append_node_attrib("Site", attrib=attribute)
255 # Call the method to write the xml file on the hard drive
256 self.xmlFile.write_tree(stylesheet = "command.xsl")
258 # Dump the config in a pyconf file in the log directory
259 logDir = src.get_log_path(self.config)
260 dumpedPyconfFileName = (self.config.VARS.datehour
262 + self.config.VARS.command
264 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
266 f = open(dumpedPyconfFilePath, 'w')
267 self.config.__save__(f)
272 def date_to_datetime(date):
274 From a string date in format YYYYMMDD_HHMMSS
275 returns list year, mon, day, hour, minutes, seconds
277 :param date str: The date in format YYYYMMDD_HHMMSS
278 :return: the same date and time in separate variables.
279 :rtype: (str,str,str,str,str,str)
287 return Y, m, dd, H, M, S
289 def timedelta_total_seconds(timedelta):
291 Replace total_seconds from datetime module
292 in order to be compatible with old python versions
294 :param timedelta datetime.timedelta: The delta between two dates
295 :return: The number of seconds corresponding to timedelta.
299 timedelta.microseconds + 0.0 +
300 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
302 def show_command_log(logFilePath, cmd, application, notShownCommands):
304 Used in updateHatXml.
305 Determine if the log xml file logFilePath
306 has to be shown or not in the hat log.
308 :param logFilePath str: the path to the command xml log file
309 :param cmd str: the command of the log file
310 :param application str: the application passed as parameter
311 to the salomeTools command
312 :param notShownCommands list: the list of commands
313 that are not shown by default
315 :return: True if cmd is not in notShownCommands and the application
316 in the log file corresponds to application
319 # When the command is not in notShownCommands, no need to go further :
321 if cmd in notShownCommands:
322 return False, None, None
324 # Get the application of the log file
326 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
327 except Exception as e:
328 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
329 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
330 return False, None, None
332 if 'application' in logFileXml.xmlroot.keys():
333 appliLog = logFileXml.xmlroot.get('application')
334 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
335 # if it corresponds, then the log has to be shown
336 if appliLog == application:
337 return True, appliLog, launched_cmd
338 elif application != 'None':
339 return False, appliLog, launched_cmd
341 return True, appliLog, launched_cmd
343 if application == 'None':
344 return True, None, None
346 return False, None, None
348 def list_log_file(dirPath, expression):
349 """Find all files corresponding to expression in dirPath
351 :param dirPath str: the directory where to search the files
352 :param expression str: the regular expression of files to find
353 :return: the list of files path and informations about it
357 for fileName in os.listdir(dirPath):
358 # YYYYMMDD_HHMMSS_namecmd.xml
360 oExpr = re.compile(sExpr)
361 if oExpr.search(fileName):
363 if fileName.startswith("micro_"):
364 file_name = fileName[len("micro_"):]
365 # get date and hour and format it
366 date_hour_cmd_host = file_name.split('_')
367 date_not_formated = date_hour_cmd_host[0]
368 date = "%s/%s/%s" % (date_not_formated[6:8],
369 date_not_formated[4:6],
370 date_not_formated[0:4])
371 hour_not_formated = date_hour_cmd_host[1]
372 hour = "%s:%s:%s" % (hour_not_formated[0:2],
373 hour_not_formated[2:4],
374 hour_not_formated[4:6])
375 if len(date_hour_cmd_host) < 4:
376 cmd = date_hour_cmd_host[2][:-len('.xml')]
379 cmd = date_hour_cmd_host[2]
380 host = date_hour_cmd_host[3][:-len('.xml')]
381 lRes.append((os.path.join(dirPath, fileName),
390 def update_hat_xml(logDir, application=None, notShownCommands = []):
392 Create the xml file in logDir that contain all the xml file
393 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
395 :param logDir str: the directory to parse
396 :param application str: the name of the application if there is any
398 # Create an instance of XmlLogFile class to create hat.xml file
399 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
400 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
401 "LOGlist", {"application" : application})
402 # parse the log directory to find all the command logs,
403 # then add it to the xml file
404 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
405 for filePath, __, date, __, hour, cmd, __ in lLogFile:
406 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
407 application, notShownCommands)
408 #if cmd not in notShownCommands:
410 # add a node to the hat.xml file
411 xmlHat.add_simple_node("LogCommand",
412 text=os.path.basename(filePath),
413 attrib = {"date" : date,
416 "application" : cmdAppli,
417 "full_command" : full_cmd})
419 # Write the file on the hard drive
420 xmlHat.write_tree('hat.xsl')
424 # prepare skip to logging logger sat5.1
425 # suppose only one logger in sat5.1
428 def getCurrentLogger():
429 """get current logging logger, set as DefaultLogger if not set yet"""
430 if len(_currentLogger) == 0:
431 import src.loggingSimple as LOGSI
432 logger = LOGSI.getDefaultLogger()
433 _currentLogger.append(logger)
434 logger.warning("set by default current logger as %s" % logger.name)
435 return _currentLogger[0]
437 def getDefaultLogger():
438 """get simple logging logger DefaultLogger, set it as current"""
439 import src.loggingSimple as LOGSI
440 logger = LOGSI.getDefaultLogger()
441 setCurrentLogger(logger) # set it as current
444 def getUnittestLogger():
445 """get simple logging logger UnittestLogger, set it as current"""
446 import src.loggingSimple as LOGSI
447 logger = LOGSI.getUnittestLogger()
448 setCurrentLogger(logger) # set it as current
451 def setCurrentLogger(logger):
452 """temporary send all in stdout as simple logging logger"""
453 if len(_currentLogger) == 0:
454 _currentLogger.append(logger)
455 logger.warning("set current logger as %s" % logger.name)
457 if _currentLogger[0].name != logger.name:
458 # logger.debug("quit current logger as default %s" % _currentLogger[0].name)
459 _currentLogger[0] = logger
460 logger.warning("change current logger as %s" % logger.name)
461 return _currentLogger[0]
463 def isCurrentLoggerUnittest():
464 logger = getCurrentLogger()
465 if "Unittest" in logger.name:
469 DBG.write("isCurrentLoggerUnittest %s" % logger.name, res)
472 def sendMessageToCurrentLogger(message, level):
474 assume relay from obsolescent
475 logger.write(msg, 1/2/3...) to future
476 logging.critical/warning/info...(msg) (as logging package tips)
478 logger = getCurrentLogger()
484 logger.critical(message)
487 logger.warning(message)
496 logger.trace(message)
499 logger.debug(message)
501 msg = "What is this level: '%s' for message:\n%s" % (level, message)