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.
35 def __init__(self, config, silent_sysstd=False, all_in_terminal=False):
38 :param config pyconf.Config: The global configuration.
39 :param silent_sysstd boolean: if True, do not write anything
43 self.default_level = 3
44 self.silentSysStd = silent_sysstd
46 # Construct xml log file location for sat prints.
47 logFileName = config.VARS.datehour + "_" + config.VARS.command + ".xml"
48 logFilePath = os.path.join(config.SITE.log.log_dir, logFileName)
49 # Construct txt file location in order to log
50 # the external commands calls (cmake, make, git clone, etc...)
51 txtFileName = config.VARS.datehour + "_" + config.VARS.command + ".txt"
52 txtFilePath = os.path.join(config.SITE.log.log_dir, "OUT", txtFileName)
54 src.ensure_path_exists(os.path.dirname(logFilePath))
55 src.ensure_path_exists(os.path.dirname(txtFilePath))
57 # The path of the log files (one for sat traces, and the other for
58 # the system commands traces)
59 self.logFileName = logFileName
60 self.logFilePath = logFilePath
61 self.txtFileName = txtFileName
62 self.txtFilePath = txtFilePath
64 # The list of all log files corresponding to the current command and
65 # the commands called by the current command
66 self.l_logFiles = [logFilePath, txtFilePath]
68 # Initialize xml instance and put first fields
69 # like beginTime, user, command, etc...
70 self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand",
71 attrib = {"application" : config.VARS.application})
72 self.put_initial_xml_fields()
73 # Initialize the txt file for reading
74 self.logTxtFile = open(str(self.txtFilePath), 'w')
75 # If the option all_in_terminal was called, all the system commands
76 # are redirected to the terminal
78 self.logTxtFile = sys.__stdout__
80 def put_initial_xml_fields(self):
81 '''Method called at class initialization : Put all fields
82 corresponding to the command context (user, time, ...)
85 self.xmlFile.add_simple_node("Site", attrib={"command" :
86 self.config.VARS.command})
87 # version of salomeTools
88 self.xmlFile.append_node_attrib("Site", attrib={"satversion" :
89 self.config.INTERNAL.sat_version})
90 # machine name on which the command has been launched
91 self.xmlFile.append_node_attrib("Site", attrib={"hostname" :
92 self.config.VARS.hostname})
93 # Distribution of the machine
94 self.xmlFile.append_node_attrib("Site", attrib={"OS" :
95 self.config.VARS.dist})
96 # The user that have launched the command
97 self.xmlFile.append_node_attrib("Site", attrib={"user" :
98 self.config.VARS.user})
99 # The time when command was launched
100 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
101 date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
102 self.xmlFile.append_node_attrib("Site", attrib={"beginTime" :
104 # The application if any
105 if "APPLICATION" in self.config:
106 self.xmlFile.append_node_attrib("Site",
107 attrib={"application" : self.config.VARS.application})
108 # The initialization of the trace node
109 self.xmlFile.add_simple_node("Log",text="")
110 # The system commands logs
111 self.xmlFile.add_simple_node("OutLog",
112 text=os.path.join("OUT", self.txtFileName))
113 # The initialization of the node where
114 # to put the links to the other sat commands that can be called by any
116 self.xmlFile.add_simple_node("Links")
122 full_launched_command):
123 '''Add a link to another log file.
125 :param log_file_name str: The file name of the link.
126 :param command_name str: The name of the command linked.
127 :param command_res str: The result of the command linked. "0" or "1"
128 :parma full_launched_command str: The full lanch command
131 xmlLinks = self.xmlFile.xmlroot.find("Links")
132 src.xmlManager.add_simple_node(xmlLinks,
134 text = log_file_name,
135 attrib = {"command" : command_name,
136 "passed" : command_res,
137 "launchedCommand" : full_launched_command})
139 def write(self, message, level=None, screenOnly=False):
140 '''the function used in the commands
141 that will print in the terminal and the log file.
143 :param message str: The message to print.
144 :param level int: The output level corresponding
145 to the message 0 < level < 6.
146 :param screenOnly boolean: if True, do not write in log file.
148 # do not write message starting with \r to log file
149 if not message.startswith("\r") and not screenOnly:
150 self.xmlFile.append_node_text("Log",
151 printcolors.cleancolor(message))
153 # get user or option output level
154 current_output_verbose_level = self.config.USER.output_verbose_level
155 if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
156 # clean the message color if the terminal is redirected by user
157 # ex: sat compile appli > log.txt
158 message = printcolors.cleancolor(message)
160 # Print message regarding the output level value
162 if level <= current_output_verbose_level and not self.silentSysStd:
163 sys.stdout.write(message)
165 if self.default_level <= current_output_verbose_level and not self.silentSysStd:
166 sys.stdout.write(message)
168 def error(self, message):
171 :param message str: The message to print.
173 # Print in the log file
174 self.xmlFile.append_node_text("traces", _('ERROR:') + message)
176 # Print in the terminal and clean colors if the terminal
177 # is redirected by user
178 if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
179 sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
181 sys.stderr.write(_('ERROR:') + message)
187 self.logTxtFile.flush()
189 def end_write(self, attribute):
190 '''Method called just after command end : Put all fields
191 corresponding to the command end context (time).
192 Write the log xml file on the hard drive.
193 And display the command to launch to get the log
195 :param attribute dict: the attribute to add to the node "Site".
197 # Get current time (end of command) and format it
198 dt = datetime.datetime.now()
199 Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
200 t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
203 total_time = timedelta_total_seconds(delta)
204 hours = int(total_time / 3600)
205 minutes = int((total_time - hours*3600) / 60)
206 seconds = total_time - hours*3600 - minutes*60
207 # Add the fields corresponding to the end time
208 # and the total time of command
209 endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
210 self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
211 self.xmlFile.append_node_attrib("Site",
212 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
214 # Add the attribute passed to the method
215 self.xmlFile.append_node_attrib("Site", attrib=attribute)
217 # Call the method to write the xml file on the hard drive
218 self.xmlFile.write_tree(stylesheet = "command.xsl")
220 # Dump the config in a pyconf file in the log directory
221 logDir = self.config.SITE.log.log_dir
222 dumpedPyconfFileName = (self.config.VARS.datehour
224 + self.config.VARS.command
226 dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
227 f = open(dumpedPyconfFilePath, 'w')
228 self.config.__save__(f)
232 def date_to_datetime(date):
233 '''Little method that gets year, mon, day, hour ,
234 minutes and seconds from a str in format YYYYMMDD_HHMMSS
236 :param date str: The date in format YYYYMMDD_HHMMSS
237 :return: the same date and time in separate variables.
238 :rtype: (str,str,str,str,str,str)
246 return Y, m, dd, H, M, S
248 def timedelta_total_seconds(timedelta):
249 '''Little method to replace total_seconds from
250 datetime module in order to be compatible with old python versions
252 :param timedelta datetime.timedelta: The delta between two dates
253 :return: The number of seconds corresponding to timedelta.
257 timedelta.microseconds + 0.0 +
258 (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
260 def show_command_log(logFilePath, cmd, application, notShownCommands):
261 '''Used in updateHatXml. Determine if the log xml file logFilePath
262 has to be shown or not in the hat log.
264 :param logFilePath str: the path to the command xml log file
265 :param cmd str: the command of the log file
266 :param application str: the application passed as parameter
267 to the salomeTools command
268 :param notShownCommands list: the list of commands
269 that are not shown by default
271 :return: True if cmd is not in notShownCommands and the application
272 in the log file corresponds to application
275 # When the command is not in notShownCommands, no need to go further :
277 if cmd in notShownCommands:
280 # Get the application of the log file
282 logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
283 except Exception as e:
284 msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
285 sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
288 if 'application' in logFileXml.xmlroot.keys():
289 appliLog = logFileXml.xmlroot.get('application')
290 # if it corresponds, then the log has to be shown
291 if appliLog == application:
292 return True, appliLog
293 elif application != 'None':
294 return False, appliLog
296 return True, appliLog
298 if application == 'None':
303 def list_log_file(dirPath, expression):
304 '''Find all files corresponding to expression in dirPath
306 :param dirPath str: the directory where to search the files
307 :param expression str: the regular expression of files to find
308 :return: the list of files path and informations about it
312 for fileName in os.listdir(dirPath):
313 # YYYYMMDD_HHMMSS_namecmd.xml
315 oExpr = re.compile(sExpr)
316 if oExpr.search(fileName):
317 # get date and hour and format it
318 date_hour_cmd = fileName.split('_')
319 date_not_formated = date_hour_cmd[0]
320 date = "%s/%s/%s" % (date_not_formated[6:8],
321 date_not_formated[4:6],
322 date_not_formated[0:4])
323 hour_not_formated = date_hour_cmd[1]
324 hour = "%s:%s:%s" % (hour_not_formated[0:2],
325 hour_not_formated[2:4],
326 hour_not_formated[4:6])
327 cmd = date_hour_cmd[2][:-len('.xml')]
328 lRes.append((os.path.join(dirPath, fileName),
329 date_not_formated, date, hour_not_formated, hour, cmd))
332 def update_hat_xml(logDir, application=None, notShownCommands = []):
333 '''Create the xml file in logDir that contain all the xml file
334 and have a name like YYYYMMDD_HHMMSS_namecmd.xml
336 :param logDir str: the directory to parse
337 :param application str: the name of the application if there is any
339 # Create an instance of XmlLogFile class to create hat.xml file
340 xmlHatFilePath = os.path.join(logDir, 'hat.xml')
341 xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
342 "LOGlist", {"application" : application})
343 # parse the log directory to find all the command logs,
344 # then add it to the xml file
345 lLogFile = list_log_file(logDir, logCommandFileExpression)
346 for filePath, _, date, _, hour, cmd in lLogFile:
347 showLog, cmdAppli = show_command_log(filePath, cmd,
348 application, notShownCommands)
349 #if cmd not in notShownCommands:
351 # add a node to the hat.xml file
352 xmlHat.add_simple_node("LogCommand",
353 text=os.path.basename(filePath),
354 attrib = {"date" : date,
357 "application" : cmdAppli})
359 # Write the file on the hard drive
360 xmlHat.write_tree('hat.xsl')