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
18 '''In this file are implemented the classes and method relative to the logging
28 from . import printcolors
29 from . import xmlManager
31 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
32 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
35 '''Class to handle log mechanism.
40 all_in_terminal=False,
41 micro_command = False):
44 :param config pyconf.Config: The global configuration.
45 :param silent_sysstd boolean: if True, do not write anything
49 self.default_level = 3
50 self.silentSysStd = silent_sysstd
52 # Construct xml log file location for sat prints.
56 hour_command_host = (config.VARS.datehour + "_" +
57 config.VARS.command + "_" +
59 logFileName = prefix + hour_command_host + ".xml"
60 log_dir = src.get_log_path(config)
61 logFilePath = os.path.join(log_dir, logFileName)
62 # Construct txt file location in order to log
63 # the external commands calls (cmake, make, git clone, etc...)
64 txtFileName = prefix + hour_command_host + ".txt"
65 txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
67 src.ensure_path_exists(os.path.dirname(logFilePath))
68 src.ensure_path_exists(os.path.dirname(txtFilePath))
70 # The path of the log files (one for sat traces, and the other for
71 # the system commands traces)
72 self.logFileName = logFileName
73 self.logFilePath = logFilePath
74 self.txtFileName = txtFileName
75 self.txtFilePath = txtFilePath
77 # The list of all log files corresponding to the current command and
78 # the commands called by the current command
79 self.l_logFiles = [logFilePath, txtFilePath]
81 # Initialize xml instance and put first fields
82 # like beginTime, user, command, etc...
83 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
84 attrib = {"application" : config.VARS.application})
85 self.put_initial_xml_fields()
86 # Initialize the txt file for reading
88 self.logTxtFile = open(str(self.txtFilePath), 'w')
90 #msg1 = _("WARNING! Trying to write to a file that"
91 # " is not accessible:")
92 #msg2 = _("The logs won't be written.")
93 #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
94 # src.printcolors.printcLabel(str(self.txtFilePath)),
95 # src.printcolors.printcWarning(msg2) ))
96 self.logTxtFile = tempfile.TemporaryFile()
98 # If the option all_in_terminal was called, all the system commands
99 # are redirected to the terminal
101 self.logTxtFile = sys.__stdout__
103 def put_initial_xml_fields(self):
104 '''Method called at class initialization : Put all fields
105 corresponding to the command context (user, time, ...)
108 self.xmlFile.add_simple_node("Site", attrib={"command" :
109 self.config.VARS.command})
110 # version of salomeTools
111 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
112 self.config.INTERNAL.sat_version})
113 # machine name on which the command has been launched
114 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
115 self.config.VARS.hostname})
116 # Distribution of the machine
117 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
118 self.config.VARS.dist})
119 # The user that have launched the command
120 self.xmlFile.append_node_attrib("Site", attrib={"user" :
121 self.config.VARS.user})
122 # The time when command was launched
123 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
124 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
125 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
127 # The application if any
128 if "APPLICATION" in self.config:
129 self.xmlFile.append_node_attrib("Site",
130 attrib={"application" : self.config.VARS.application})
131 # The initialization of the trace node
132 self.xmlFile.add_simple_node("Log",text="")
133 # The system commands logs
134 self.xmlFile.add_simple_node("OutLog",
135 text=os.path.join("OUT", self.txtFileName))
136 # The initialization of the node where
137 # to put the links to the other sat commands that can be called by any
139 self.xmlFile.add_simple_node("Links")
145 full_launched_command):
146 '''Add a link to another log file.
148 :param log_file_name str: The file name of the link.
149 :param command_name str: The name of the command linked.
150 :param command_res str: The result of the command linked. "0" or "1"
151 :parma full_launched_command str: The full lanch command
154 xmlLinks = self.xmlFile.xmlroot.find("Links")
155 src.xmlManager.add_simple_node(xmlLinks,
157 text = log_file_name,
158 attrib = {"command" : command_name,
159 "passed" : command_res,
160 "launchedCommand" : full_launched_command})
162 def write(self, message, level=None, screenOnly=False):
163 '''the function used in the commands
164 that will print in the terminal and the log file.
166 :param message str: The message to print.
167 :param level int: The output level corresponding
168 to the message 0 < level < 6.
169 :param screenOnly boolean: if True, do not write in log file.
171 # do not write message starting with \r to log file
172 if not message.startswith("\r") and not screenOnly:
173 self.xmlFile.append_node_text("Log",
174 printcolors.cleancolor(message))
176 # get user or option output level
177 current_output_verbose_level = self.config.USER.output_verbose_level
178 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
179 # clean the message color if the terminal is redirected by user
180 # ex: sat compile appli > log.txt
181 message = printcolors.cleancolor(message)
183 # Print message regarding the output level value
185 if level <= current_output_verbose_level and not self.silentSysStd:
186 sys.stdout.write(message)
188 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
189 sys.stdout.write(message)
192 def error(self, message):
195 :param message str: The message to print.
197 # Print in the log file
198 self.xmlFile.append_node_text("traces", _('ERROR:') + message)
200 # Print in the terminal and clean colors if the terminal
201 # is redirected by user
202 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
203 sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
205 sys.stderr.write(_('ERROR:') + message)
211 self.logTxtFile.flush()
213 def end_write(self, attribute):
214 '''Method called just after command end : Put all fields
215 corresponding to the command end context (time).
216 Write the log xml file on the hard drive.
217 And display the command to launch to get the log
219 :param attribute dict: the attribute to add to the node "Site".
221 # Get current time (end of command) and format it
222 dt = datetime.datetime.now()
223 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
224 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
227 total_time = timedelta_total_seconds(delta)
228 hours = int(total_time / 3600)
229 minutes = int((total_time - hours*3600) / 60)
230 seconds = total_time - hours*3600 - minutes*60
231 # Add the fields corresponding to the end time
232 # and the total time of command
233 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
234 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
235 self.xmlFile.append_node_attrib("Site",
236 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
238 # Add the attribute passed to the method
239 self.xmlFile.append_node_attrib("Site", attrib=attribute)
241 # Call the method to write the xml file on the hard drive
242 self.xmlFile.write_tree(stylesheet = "command.xsl")
244 # Dump the config in a pyconf file in the log directory
245 logDir = src.get_log_path(self.config)
246 dumpedPyconfFileName = (self.config.VARS.datehour
248 + self.config.VARS.command
250 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
252 f = open(dumpedPyconfFilePath, 'w')
253 self.config.__save__(f)
258 def date_to_datetime(date):
259 '''Little method that gets year, mon, day, hour ,
260 minutes and seconds from a str in format YYYYMMDD_HHMMSS
262 :param date str: The date in format YYYYMMDD_HHMMSS
263 :return: the same date and time in separate variables.
264 :rtype: (str,str,str,str,str,str)
272 return Y, m, dd, H, M, S
274 def timedelta_total_seconds(timedelta):
275 '''Little method to replace total_seconds from
276 datetime module in order to be compatible with old python versions
278 :param timedelta datetime.timedelta: The delta between two dates
279 :return: The number of seconds corresponding to timedelta.
283 timedelta.microseconds + 0.0 +
284 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
286 def show_command_log(logFilePath, cmd, application, notShownCommands):
287 '''Used in updateHatXml. Determine if the log xml file logFilePath
288 has to be shown or not in the hat log.
290 :param logFilePath str: the path to the command xml log file
291 :param cmd str: the command of the log file
292 :param application str: the application passed as parameter
293 to the salomeTools command
294 :param notShownCommands list: the list of commands
295 that are not shown by default
297 :return: True if cmd is not in notShownCommands and the application
298 in the log file corresponds to application
301 # When the command is not in notShownCommands, no need to go further :
303 if cmd in notShownCommands:
304 return False, None, None
306 # Get the application of the log file
308 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
309 except Exception as e:
310 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
311 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
312 return False, None, None
314 if 'application' in logFileXml.xmlroot.keys():
315 appliLog = logFileXml.xmlroot.get('application')
316 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
317 # if it corresponds, then the log has to be shown
318 if appliLog == application:
319 return True, appliLog, launched_cmd
320 elif application != 'None':
321 return False, appliLog, launched_cmd
323 return True, appliLog, launched_cmd
325 if application == 'None':
326 return True, None, None
328 return False, None, None
330 def list_log_file(dirPath, expression):
331 '''Find all files corresponding to expression in dirPath
333 :param dirPath str: the directory where to search the files
334 :param expression str: the regular expression of files to find
335 :return: the list of files path and informations about it
339 for fileName in os.listdir(dirPath):
340 # YYYYMMDD_HHMMSS_namecmd.xml
342 oExpr = re.compile(sExpr)
343 if oExpr.search(fileName):
345 if fileName.startswith("micro_"):
346 file_name = fileName[len("micro_"):]
347 # get date and hour and format it
348 date_hour_cmd_host = file_name.split('_')
349 date_not_formated = date_hour_cmd_host[0]
350 date = "%s/%s/%s" % (date_not_formated[6:8],
351 date_not_formated[4:6],
352 date_not_formated[0:4])
353 hour_not_formated = date_hour_cmd_host[1]
354 hour = "%s:%s:%s" % (hour_not_formated[0:2],
355 hour_not_formated[2:4],
356 hour_not_formated[4:6])
357 if len(date_hour_cmd_host) < 4:
358 cmd = date_hour_cmd_host[2][:-len('.xml')]
361 cmd = date_hour_cmd_host[2]
362 host = date_hour_cmd_host[3][:-len('.xml')]
363 lRes.append((os.path.join(dirPath, fileName),
372 def update_hat_xml(logDir, application=None, notShownCommands = []):
373 '''Create the xml file in logDir that contain all the xml file
374 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
376 :param logDir str: the directory to parse
377 :param application str: the name of the application if there is any
379 # Create an instance of XmlLogFile class to create hat.xml file
380 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
381 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
382 "LOGlist", {"application" : application})
383 # parse the log directory to find all the command logs,
384 # then add it to the xml file
385 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
386 for filePath, __, date, __, hour, cmd, __ in lLogFile:
387 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
388 application, notShownCommands)
389 #if cmd not in notShownCommands:
391 # add a node to the hat.xml file
392 xmlHat.add_simple_node("LogCommand",
393 text=os.path.basename(filePath),
394 attrib = {"date" : date,
397 "application" : cmdAppli,
398 "full_command" : full_cmd})
400 # Write the file on the hard drive
401 xmlHat.write_tree('hat.xsl')