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 logCommandFileExpression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
33 '''Class to handle log mechanism.
38 all_in_terminal=False,
39 micro_command = False):
42 :param config pyconf.Config: The global configuration.
43 :param silent_sysstd boolean: if True, do not write anything
47 self.default_level = 3
48 self.silentSysStd = silent_sysstd
50 # Construct xml log file location for sat prints.
54 hour_command = config.VARS.datehour + "_" + config.VARS.command
55 logFileName = prefix + hour_command + ".xml"
56 logFilePath = os.path.join(config.USER.log_dir, logFileName)
57 # Construct txt file location in order to log
58 # the external commands calls (cmake, make, git clone, etc...)
59 txtFileName = prefix + hour_command + ".txt"
60 txtFilePath = os.path.join(config.USER.log_dir, "OUT", txtFileName)
62 src.ensure_path_exists(os.path.dirname(logFilePath))
63 src.ensure_path_exists(os.path.dirname(txtFilePath))
65 # The path of the log files (one for sat traces, and the other for
66 # the system commands traces)
67 self.logFileName = logFileName
68 self.logFilePath = logFilePath
69 self.txtFileName = txtFileName
70 self.txtFilePath = txtFilePath
72 # The list of all log files corresponding to the current command and
73 # the commands called by the current command
74 self.l_logFiles = [logFilePath, txtFilePath]
76 # Initialize xml instance and put first fields
77 # like beginTime, user, command, etc...
78 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
79 attrib = {"application" : config.VARS.application})
80 self.put_initial_xml_fields()
81 # Initialize the txt file for reading
82 self.logTxtFile = open(str(self.txtFilePath), 'w')
83 # If the option all_in_terminal was called, all the system commands
84 # are redirected to the terminal
86 self.logTxtFile = sys.__stdout__
88 def put_initial_xml_fields(self):
89 '''Method called at class initialization : Put all fields
90 corresponding to the command context (user, time, ...)
93 self.xmlFile.add_simple_node("Site", attrib={"command" :
94 self.config.VARS.command})
95 # version of salomeTools
96 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
97 self.config.INTERNAL.sat_version})
98 # machine name on which the command has been launched
99 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
100 self.config.VARS.hostname})
101 # Distribution of the machine
102 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
103 self.config.VARS.dist})
104 # The user that have launched the command
105 self.xmlFile.append_node_attrib("Site", attrib={"user" :
106 self.config.VARS.user})
107 # The time when command was launched
108 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
109 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
110 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
112 # The application if any
113 if "APPLICATION" in self.config:
114 self.xmlFile.append_node_attrib("Site",
115 attrib={"application" : self.config.VARS.application})
116 # The initialization of the trace node
117 self.xmlFile.add_simple_node("Log",text="")
118 # The system commands logs
119 self.xmlFile.add_simple_node("OutLog",
120 text=os.path.join("OUT", self.txtFileName))
121 # The initialization of the node where
122 # to put the links to the other sat commands that can be called by any
124 self.xmlFile.add_simple_node("Links")
130 full_launched_command):
131 '''Add a link to another log file.
133 :param log_file_name str: The file name of the link.
134 :param command_name str: The name of the command linked.
135 :param command_res str: The result of the command linked. "0" or "1"
136 :parma full_launched_command str: The full lanch command
139 xmlLinks = self.xmlFile.xmlroot.find("Links")
140 src.xmlManager.add_simple_node(xmlLinks,
142 text = log_file_name,
143 attrib = {"command" : command_name,
144 "passed" : command_res,
145 "launchedCommand" : full_launched_command})
147 def write(self, message, level=None, screenOnly=False):
148 '''the function used in the commands
149 that will print in the terminal and the log file.
151 :param message str: The message to print.
152 :param level int: The output level corresponding
153 to the message 0 < level < 6.
154 :param screenOnly boolean: if True, do not write in log file.
156 # do not write message starting with \r to log file
157 if not message.startswith("\r") and not screenOnly:
158 self.xmlFile.append_node_text("Log",
159 printcolors.cleancolor(message))
161 # get user or option output level
162 current_output_verbose_level = self.config.USER.output_verbose_level
163 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
164 # clean the message color if the terminal is redirected by user
165 # ex: sat compile appli > log.txt
166 message = printcolors.cleancolor(message)
168 # Print message regarding the output level value
170 if level <= current_output_verbose_level and not self.silentSysStd:
171 sys.stdout.write(message)
173 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
174 sys.stdout.write(message)
177 def error(self, message):
180 :param message str: The message to print.
182 # Print in the log file
183 self.xmlFile.append_node_text("traces", _('ERROR:') + message)
185 # Print in the terminal and clean colors if the terminal
186 # is redirected by user
187 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
188 sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
190 sys.stderr.write(_('ERROR:') + message)
196 self.logTxtFile.flush()
198 def end_write(self, attribute):
199 '''Method called just after command end : Put all fields
200 corresponding to the command end context (time).
201 Write the log xml file on the hard drive.
202 And display the command to launch to get the log
204 :param attribute dict: the attribute to add to the node "Site".
206 # Get current time (end of command) and format it
207 dt = datetime.datetime.now()
208 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
209 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
212 total_time = timedelta_total_seconds(delta)
213 hours = int(total_time / 3600)
214 minutes = int((total_time - hours*3600) / 60)
215 seconds = total_time - hours*3600 - minutes*60
216 # Add the fields corresponding to the end time
217 # and the total time of command
218 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
219 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
220 self.xmlFile.append_node_attrib("Site",
221 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
223 # Add the attribute passed to the method
224 self.xmlFile.append_node_attrib("Site", attrib=attribute)
226 # Call the method to write the xml file on the hard drive
227 self.xmlFile.write_tree(stylesheet = "command.xsl")
229 # Dump the config in a pyconf file in the log directory
230 logDir = self.config.USER.log_dir
231 dumpedPyconfFileName = (self.config.VARS.datehour
233 + self.config.VARS.command
235 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
236 f = open(dumpedPyconfFilePath, 'w')
237 self.config.__save__(f)
241 def date_to_datetime(date):
242 '''Little method that gets year, mon, day, hour ,
243 minutes and seconds from a str in format YYYYMMDD_HHMMSS
245 :param date str: The date in format YYYYMMDD_HHMMSS
246 :return: the same date and time in separate variables.
247 :rtype: (str,str,str,str,str,str)
255 return Y, m, dd, H, M, S
257 def timedelta_total_seconds(timedelta):
258 '''Little method to replace total_seconds from
259 datetime module in order to be compatible with old python versions
261 :param timedelta datetime.timedelta: The delta between two dates
262 :return: The number of seconds corresponding to timedelta.
266 timedelta.microseconds + 0.0 +
267 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
269 def show_command_log(logFilePath, cmd, application, notShownCommands):
270 '''Used in updateHatXml. Determine if the log xml file logFilePath
271 has to be shown or not in the hat log.
273 :param logFilePath str: the path to the command xml log file
274 :param cmd str: the command of the log file
275 :param application str: the application passed as parameter
276 to the salomeTools command
277 :param notShownCommands list: the list of commands
278 that are not shown by default
280 :return: True if cmd is not in notShownCommands and the application
281 in the log file corresponds to application
284 # When the command is not in notShownCommands, no need to go further :
286 if cmd in notShownCommands:
289 # Get the application of the log file
291 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
292 except Exception as e:
293 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
294 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
297 if 'application' in logFileXml.xmlroot.keys():
298 appliLog = logFileXml.xmlroot.get('application')
299 # if it corresponds, then the log has to be shown
300 if appliLog == application:
301 return True, appliLog
302 elif application != 'None':
303 return False, appliLog
305 return True, appliLog
307 if application == 'None':
312 def list_log_file(dirPath, expression):
313 '''Find all files corresponding to expression in dirPath
315 :param dirPath str: the directory where to search the files
316 :param expression str: the regular expression of files to find
317 :return: the list of files path and informations about it
321 for fileName in os.listdir(dirPath):
322 # YYYYMMDD_HHMMSS_namecmd.xml
324 oExpr = re.compile(sExpr)
325 if oExpr.search(fileName):
326 # get date and hour and format it
327 date_hour_cmd = fileName.split('_')
328 date_not_formated = date_hour_cmd[0]
329 date = "%s/%s/%s" % (date_not_formated[6:8],
330 date_not_formated[4:6],
331 date_not_formated[0:4])
332 hour_not_formated = date_hour_cmd[1]
333 hour = "%s:%s:%s" % (hour_not_formated[0:2],
334 hour_not_formated[2:4],
335 hour_not_formated[4:6])
336 cmd = date_hour_cmd[2][:-len('.xml')]
337 lRes.append((os.path.join(dirPath, fileName),
338 date_not_formated, date, hour_not_formated, hour, cmd))
341 def update_hat_xml(logDir, application=None, notShownCommands = []):
342 '''Create the xml file in logDir that contain all the xml file
343 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
345 :param logDir str: the directory to parse
346 :param application str: the name of the application if there is any
348 # Create an instance of XmlLogFile class to create hat.xml file
349 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
350 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
351 "LOGlist", {"application" : application})
352 # parse the log directory to find all the command logs,
353 # then add it to the xml file
354 lLogFile = list_log_file(logDir, logCommandFileExpression)
355 for filePath, _, date, _, hour, cmd in lLogFile:
356 showLog, cmdAppli = show_command_log(filePath, cmd,
357 application, notShownCommands)
358 #if cmd not in notShownCommands:
360 # add a node to the hat.xml file
361 xmlHat.add_simple_node("LogCommand",
362 text=os.path.basename(filePath),
363 attrib = {"date" : date,
366 "application" : cmdAppli})
368 # Write the file on the hard drive
369 xmlHat.write_tree('hat.xsl')