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
27 from . import printcolors
28 from . import xmlManager
30 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
31 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
34 '''Class to handle log mechanism.
39 all_in_terminal=False,
40 micro_command = False):
43 :param config pyconf.Config: The global configuration.
44 :param silent_sysstd boolean: if True, do not write anything
48 self.default_level = 3
49 self.silentSysStd = silent_sysstd
51 # Construct xml log file location for sat prints.
55 hour_command_host = (config.VARS.datehour + "_" +
56 config.VARS.command + "_" +
58 logFileName = prefix + hour_command_host + ".xml"
59 logFilePath = os.path.join(config.USER.log_dir, logFileName)
60 # Construct txt file location in order to log
61 # the external commands calls (cmake, make, git clone, etc...)
62 txtFileName = prefix + hour_command_host + ".txt"
63 txtFilePath = os.path.join(config.USER.log_dir, "OUT", txtFileName)
65 src.ensure_path_exists(os.path.dirname(logFilePath))
66 src.ensure_path_exists(os.path.dirname(txtFilePath))
68 # The path of the log files (one for sat traces, and the other for
69 # the system commands traces)
70 self.logFileName = logFileName
71 self.logFilePath = logFilePath
72 self.txtFileName = txtFileName
73 self.txtFilePath = txtFilePath
75 # The list of all log files corresponding to the current command and
76 # the commands called by the current command
77 self.l_logFiles = [logFilePath, txtFilePath]
79 # Initialize xml instance and put first fields
80 # like beginTime, user, command, etc...
81 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
82 attrib = {"application" : config.VARS.application})
83 self.put_initial_xml_fields()
84 # Initialize the txt file for reading
85 self.logTxtFile = open(str(self.txtFilePath), 'w')
86 # If the option all_in_terminal was called, all the system commands
87 # are redirected to the terminal
89 self.logTxtFile = sys.__stdout__
91 def put_initial_xml_fields(self):
92 '''Method called at class initialization : Put all fields
93 corresponding to the command context (user, time, ...)
96 self.xmlFile.add_simple_node("Site", attrib={"command" :
97 self.config.VARS.command})
98 # version of salomeTools
99 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
100 self.config.INTERNAL.sat_version})
101 # machine name on which the command has been launched
102 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
103 self.config.VARS.hostname})
104 # Distribution of the machine
105 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
106 self.config.VARS.dist})
107 # The user that have launched the command
108 self.xmlFile.append_node_attrib("Site", attrib={"user" :
109 self.config.VARS.user})
110 # The time when command was launched
111 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
112 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
113 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
115 # The application if any
116 if "APPLICATION" in self.config:
117 self.xmlFile.append_node_attrib("Site",
118 attrib={"application" : self.config.VARS.application})
119 # The initialization of the trace node
120 self.xmlFile.add_simple_node("Log",text="")
121 # The system commands logs
122 self.xmlFile.add_simple_node("OutLog",
123 text=os.path.join("OUT", self.txtFileName))
124 # The initialization of the node where
125 # to put the links to the other sat commands that can be called by any
127 self.xmlFile.add_simple_node("Links")
133 full_launched_command):
134 '''Add a link to another log file.
136 :param log_file_name str: The file name of the link.
137 :param command_name str: The name of the command linked.
138 :param command_res str: The result of the command linked. "0" or "1"
139 :parma full_launched_command str: The full lanch command
142 xmlLinks = self.xmlFile.xmlroot.find("Links")
143 src.xmlManager.add_simple_node(xmlLinks,
145 text = log_file_name,
146 attrib = {"command" : command_name,
147 "passed" : command_res,
148 "launchedCommand" : full_launched_command})
150 def write(self, message, level=None, screenOnly=False):
151 '''the function used in the commands
152 that will print in the terminal and the log file.
154 :param message str: The message to print.
155 :param level int: The output level corresponding
156 to the message 0 < level < 6.
157 :param screenOnly boolean: if True, do not write in log file.
159 # do not write message starting with \r to log file
160 if not message.startswith("\r") and not screenOnly:
161 self.xmlFile.append_node_text("Log",
162 printcolors.cleancolor(message))
164 # get user or option output level
165 current_output_verbose_level = self.config.USER.output_verbose_level
166 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
167 # clean the message color if the terminal is redirected by user
168 # ex: sat compile appli > log.txt
169 message = printcolors.cleancolor(message)
171 # Print message regarding the output level value
173 if level <= current_output_verbose_level and not self.silentSysStd:
174 sys.stdout.write(message)
176 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
177 sys.stdout.write(message)
180 def error(self, message):
183 :param message str: The message to print.
185 # Print in the log file
186 self.xmlFile.append_node_text("traces", _('ERROR:') + message)
188 # Print in the terminal and clean colors if the terminal
189 # is redirected by user
190 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
191 sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
193 sys.stderr.write(_('ERROR:') + message)
199 self.logTxtFile.flush()
201 def end_write(self, attribute):
202 '''Method called just after command end : Put all fields
203 corresponding to the command end context (time).
204 Write the log xml file on the hard drive.
205 And display the command to launch to get the log
207 :param attribute dict: the attribute to add to the node "Site".
209 # Get current time (end of command) and format it
210 dt = datetime.datetime.now()
211 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
212 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
215 total_time = timedelta_total_seconds(delta)
216 hours = int(total_time / 3600)
217 minutes = int((total_time - hours*3600) / 60)
218 seconds = total_time - hours*3600 - minutes*60
219 # Add the fields corresponding to the end time
220 # and the total time of command
221 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
222 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
223 self.xmlFile.append_node_attrib("Site",
224 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
226 # Add the attribute passed to the method
227 self.xmlFile.append_node_attrib("Site", attrib=attribute)
229 # Call the method to write the xml file on the hard drive
230 self.xmlFile.write_tree(stylesheet = "command.xsl")
232 # Dump the config in a pyconf file in the log directory
233 logDir = self.config.USER.log_dir
234 dumpedPyconfFileName = (self.config.VARS.datehour
236 + self.config.VARS.command
238 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
239 f = open(dumpedPyconfFilePath, 'w')
240 self.config.__save__(f)
244 def date_to_datetime(date):
245 '''Little method that gets year, mon, day, hour ,
246 minutes and seconds from a str in format YYYYMMDD_HHMMSS
248 :param date str: The date in format YYYYMMDD_HHMMSS
249 :return: the same date and time in separate variables.
250 :rtype: (str,str,str,str,str,str)
258 return Y, m, dd, H, M, S
260 def timedelta_total_seconds(timedelta):
261 '''Little method to replace total_seconds from
262 datetime module in order to be compatible with old python versions
264 :param timedelta datetime.timedelta: The delta between two dates
265 :return: The number of seconds corresponding to timedelta.
269 timedelta.microseconds + 0.0 +
270 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
272 def show_command_log(logFilePath, cmd, application, notShownCommands):
273 '''Used in updateHatXml. Determine if the log xml file logFilePath
274 has to be shown or not in the hat log.
276 :param logFilePath str: the path to the command xml log file
277 :param cmd str: the command of the log file
278 :param application str: the application passed as parameter
279 to the salomeTools command
280 :param notShownCommands list: the list of commands
281 that are not shown by default
283 :return: True if cmd is not in notShownCommands and the application
284 in the log file corresponds to application
287 # When the command is not in notShownCommands, no need to go further :
289 if cmd in notShownCommands:
290 return False, None, None
292 # Get the application of the log file
294 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
295 except Exception as e:
296 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
297 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
298 return False, None, None
300 if 'application' in logFileXml.xmlroot.keys():
301 appliLog = logFileXml.xmlroot.get('application')
302 launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
303 # if it corresponds, then the log has to be shown
304 if appliLog == application:
305 return True, appliLog, launched_cmd
306 elif application != 'None':
307 return False, appliLog, launched_cmd
309 return True, appliLog, launched_cmd
311 if application == 'None':
312 return True, None, None
314 return False, None, None
316 def list_log_file(dirPath, expression):
317 '''Find all files corresponding to expression in dirPath
319 :param dirPath str: the directory where to search the files
320 :param expression str: the regular expression of files to find
321 :return: the list of files path and informations about it
325 for fileName in os.listdir(dirPath):
326 # YYYYMMDD_HHMMSS_namecmd.xml
328 oExpr = re.compile(sExpr)
329 if oExpr.search(fileName):
331 if fileName.startswith("micro_"):
332 file_name = fileName[len("micro_"):]
333 # get date and hour and format it
334 date_hour_cmd_host = file_name.split('_')
335 date_not_formated = date_hour_cmd_host[0]
336 date = "%s/%s/%s" % (date_not_formated[6:8],
337 date_not_formated[4:6],
338 date_not_formated[0:4])
339 hour_not_formated = date_hour_cmd_host[1]
340 hour = "%s:%s:%s" % (hour_not_formated[0:2],
341 hour_not_formated[2:4],
342 hour_not_formated[4:6])
343 if len(date_hour_cmd_host) < 4:
344 cmd = date_hour_cmd_host[2][:-len('.xml')]
347 cmd = date_hour_cmd_host[2]
348 host = date_hour_cmd_host[3][:-len('.xml')]
349 lRes.append((os.path.join(dirPath, fileName),
358 def update_hat_xml(logDir, application=None, notShownCommands = []):
359 '''Create the xml file in logDir that contain all the xml file
360 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
362 :param logDir str: the directory to parse
363 :param application str: the name of the application if there is any
365 # Create an instance of XmlLogFile class to create hat.xml file
366 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
367 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
368 "LOGlist", {"application" : application})
369 # parse the log directory to find all the command logs,
370 # then add it to the xml file
371 lLogFile = list_log_file(logDir, log_macro_command_file_expression)
372 for filePath, __, date, __, hour, cmd, __ in lLogFile:
373 showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
374 application, notShownCommands)
375 #if cmd not in notShownCommands:
377 # add a node to the hat.xml file
378 xmlHat.add_simple_node("LogCommand",
379 text=os.path.basename(filePath),
380 attrib = {"date" : date,
383 "application" : cmdAppli,
384 "full_command" : full_cmd})
386 # Write the file on the hard drive
387 xmlHat.write_tree('hat.xsl')