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):
207 :param message str: The message to print.
209 # Print in the log file
210 self.xmlFile.append_node_text("traces", _('ERROR:') + 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(_('ERROR:') + message))
217 sys.stderr.write(_('ERROR:') + message)
222 self.logTxtFile.flush()
224 def end_write(self, attribute):
226 Called just after command end: Put all fields
227 corresponding to the command end context (time).
228 Write the log xml file on the hard drive.
229 And display the command to launch to get the log
231 :param attribute dict: the attribute to add to the node "Site".
233 # Get current time (end of command) and format it
234 dt = datetime.datetime.now()
235 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
236 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
239 total_time = timedelta_total_seconds(delta)
240 hours = int(total_time / 3600)
241 minutes = int((total_time - hours*3600) / 60)
242 seconds = total_time - hours*3600 - minutes*60
243 # Add the fields corresponding to the end time
244 # and the total time of command
245 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
246 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
247 self.xmlFile.append_node_attrib("Site",
248 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
250 # Add the attribute passed to the method
251 self.xmlFile.append_node_attrib("Site", attrib=attribute)
253 # Call the method to write the xml file on the hard drive
254 self.xmlFile.write_tree(stylesheet = "command.xsl")
256 # so unconditionnaly copy stylesheet file(s)
257 xslDir = os.path.join(self.config.VARS.srcDir, 'xsl')
258 xslCommand = "command.xsl"
259 # xslHat = "hat.xsl" # have to be completed (one time at end)
261 imgLogo = "LOGO-SAT.png"
262 files_to_copy = [xslCommand, xsltest, imgLogo]
264 logDir = src.get_log_path(self.config)
265 # copy the stylesheets in the log directory as soon as possible here
266 # because referenced in self.xmlFile.write_tree above
267 # OP We use copy instead of copy2 to update the creation date
268 # So we can clean the LOGS directories easily
269 for f in files_to_copy:
270 f_init = os.path.join(xslDir, f)
271 f_target = os.path.join(logDir, f)
272 if not os.path.isfile(f_target): # do not overrride
273 shutil.copy(f_init, logDir)
275 # Dump the config in a pyconf file in the log directory
276 dumpedPyconfFileName = (self.config.VARS.datehour
278 + self.config.VARS.command
280 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
282 f = open(dumpedPyconfFilePath, 'w')
283 self.config.__save__(f)
288 def date_to_datetime(date):
290 From a string date in format YYYYMMDD_HHMMSS
291 returns list year, mon, day, hour, minutes, seconds
293 :param date str: The date in format YYYYMMDD_HHMMSS
294 :return: the same date and time in separate variables.
295 :rtype: (str,str,str,str,str,str)
303 return Y, m, dd, H, M, S
305 def timedelta_total_seconds(timedelta):
307 Replace total_seconds from datetime module
308 in order to be compatible with old python versions
310 :param timedelta datetime.timedelta: The delta between two dates
311 :return: The number of seconds corresponding to timedelta.
315 timedelta.microseconds + 0.0 +
316 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
318 def show_command_log(logFilePath, cmd, application, notShownCommands):
320 Used in updateHatXml.
321 Determine if the log xml file logFilePath
322 has to be shown or not in the hat log.
324 :param logFilePath str: the path to the command xml log file
325 :param cmd str: the command of the log file
326 :param application str: the application passed as parameter
327 to the salomeTools command
328 :param notShownCommands list: the list of commands
329 that are not shown by default
331 :return: True if cmd is not in notShownCommands and the application
332 in the log file corresponds to application
335 # When the command is not in notShownCommands, no need to go further :
337 if cmd in notShownCommands:
338 return False, None, None
340 # Get the application of the log file
342 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
343 except Exception as e:
344 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
345 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
346 return False, None, None
348 if 'application' in logFileXml.xmlroot.keys():
349 appliLog = logFileXml.xmlroot.get('application')
350 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
351 # if it corresponds, then the log has to be shown
352 if appliLog == application:
353 return True, appliLog, launched_cmd
354 elif application != 'None':
355 return False, appliLog, launched_cmd
357 return True, appliLog, launched_cmd
359 if application == 'None':
360 return True, None, None
362 return False, None, None
364 def list_log_file(dirPath, expression):
365 """Find all files corresponding to expression in dirPath
367 :param dirPath str: the directory where to search the files
368 :param expression str: the regular expression of files to find
369 :return: the list of files path and informations about it
373 for fileName in os.listdir(dirPath):
374 # YYYYMMDD_HHMMSS_namecmd.xml
376 oExpr = re.compile(sExpr)
377 if oExpr.search(fileName):
379 if fileName.startswith("micro_"):
380 file_name = fileName[len("micro_"):]
381 # get date and hour and format it
382 date_hour_cmd_host = file_name.split('_')
383 date_not_formated = date_hour_cmd_host[0]
384 date = "%s/%s/%s" % (date_not_formated[6:8],
385 date_not_formated[4:6],
386 date_not_formated[0:4])
387 hour_not_formated = date_hour_cmd_host[1]
388 hour = "%s:%s:%s" % (hour_not_formated[0:2],
389 hour_not_formated[2:4],
390 hour_not_formated[4:6])
391 if len(date_hour_cmd_host) < 4:
392 cmd = date_hour_cmd_host[2][:-len('.xml')]
395 cmd = date_hour_cmd_host[2]
396 host = date_hour_cmd_host[3][:-len('.xml')]
397 lRes.append((os.path.join(dirPath, fileName),
406 def update_hat_xml(logDir, application=None, notShownCommands = []):
408 Create the xml file in logDir that contain all the xml file
409 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
411 :param logDir str: the directory to parse
412 :param application str: the name of the application if there is any
414 # Create an instance of XmlLogFile class to create hat.xml file
415 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
416 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
417 "LOGlist", {"application" : application})
418 # parse the log directory to find all the command logs,
419 # then add it to the xml file
420 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
421 for filePath, __, date, __, hour, cmd, __ in lLogFile:
422 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
423 application, notShownCommands)
424 #if cmd not in notShownCommands:
426 # add a node to the hat.xml file
427 xmlHat.add_simple_node("LogCommand",
428 text=os.path.basename(filePath),
429 attrib = {"date" : date,
432 "application" : cmdAppli,
433 "full_command" : full_cmd})
435 # Write the file on the hard drive
436 xmlHat.write_tree('hat.xsl')
440 # prepare skip to logging logger sat5.1
441 # suppose only one logger in sat5.1
444 def getCurrentLogger():
445 """get current logging logger, set as DefaultLogger if not set yet"""
446 if len(_currentLogger) == 0:
447 import src.loggingSimple as LOGSI
448 logger = LOGSI.getDefaultLogger()
449 _currentLogger.append(logger)
450 logger.warning("set by default current logger as %s" % logger.name)
451 return _currentLogger[0]
453 def getDefaultLogger():
454 """get simple logging logger DefaultLogger, set it as current"""
455 import src.loggingSimple as LOGSI
456 logger = LOGSI.getDefaultLogger()
457 setCurrentLogger(logger) # set it as current
460 def getUnittestLogger():
461 """get simple logging logger UnittestLogger, set it as current"""
462 import src.loggingSimple as LOGSI
463 logger = LOGSI.getUnittestLogger()
464 setCurrentLogger(logger) # set it as current
467 def setCurrentLogger(logger):
468 """temporary send all in stdout as simple logging logger"""
469 if len(_currentLogger) == 0:
470 _currentLogger.append(logger)
471 logger.debug("set current logger as %s" % logger.name)
473 if _currentLogger[0].name != logger.name:
474 # logger.debug("quit current logger as default %s" % _currentLogger[0].name)
475 _currentLogger[0] = logger
476 logger.warning("change current logger as %s" % logger.name)
477 return _currentLogger[0]
479 def isCurrentLoggerUnittest():
480 logger = getCurrentLogger()
481 if "Unittest" in logger.name:
485 DBG.write("isCurrentLoggerUnittest %s" % logger.name, res)
488 def sendMessageToCurrentLogger(message, level):
490 assume relay from obsolescent
491 logger.write(msg, 1/2/3...) to future
492 logging.critical/warning/info...(msg) (as logging package tips)
494 logger = getCurrentLogger()
500 logger.critical(message)
503 logger.warning(message)
512 logger.trace(message)
515 logger.debug(message)
517 msg = "What is this level: '%s' for message:\n%s" % (level, message)