]> SALOME platform Git repositories - tools/sat.git/blob - src/logger.py
Salome HOME
eb2ee58b283a4b3e5e3a227e301ce1822f23f586
[tools/sat.git] / src / logger.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
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.
9 #
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.
14 #
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
19 '''
20
21 import sys
22 import os
23 import datetime
24 import re
25
26 import src
27 from . import printcolors
28 from . import xmlManager
29
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$"
32
33 class Logger(object):
34     '''Class to handle log mechanism.
35     '''
36     def __init__(self,
37                  config,
38                  silent_sysstd=False,
39                  all_in_terminal=False,
40                  micro_command = False):
41         '''Initialization
42         
43         :param config pyconf.Config: The global configuration.
44         :param silent_sysstd boolean: if True, do not write anything
45                                       in terminal.
46         '''
47         self.config = config
48         self.default_level = 3
49         self.silentSysStd = silent_sysstd
50         
51         # Construct xml log file location for sat prints.
52         prefix = ""
53         if micro_command:
54             prefix = "micro_"
55         hour_command_host = (config.VARS.datehour + "_" + 
56                              config.VARS.command + "_" + 
57                              config.VARS.hostname)
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)
64         
65         src.ensure_path_exists(os.path.dirname(logFilePath))
66         src.ensure_path_exists(os.path.dirname(txtFilePath))
67         
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
74         
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]
78         
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
88         if all_in_terminal:
89             self.logTxtFile = sys.__stdout__
90         
91     def put_initial_xml_fields(self):
92         '''Method called at class initialization : Put all fields 
93            corresponding to the command context (user, time, ...)
94         '''
95         # command name
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" : 
114                                                         date_hour})
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
126         # command 
127         self.xmlFile.add_simple_node("Links")
128
129     def add_link(self,
130                  log_file_name,
131                  command_name,
132                  command_res,
133                  full_launched_command):
134         '''Add a link to another log file.
135         
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 
140                                           ("sat command ...")
141         '''
142         xmlLinks = self.xmlFile.xmlroot.find("Links")
143         src.xmlManager.add_simple_node(xmlLinks,
144                                        "link", 
145                                        text = log_file_name,
146                                        attrib = {"command" : command_name,
147                                                  "passed" : command_res,
148                                            "launchedCommand" : full_launched_command})
149
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.
153         
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.
158         '''
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))
163
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)
170         
171         # Print message regarding the output level value
172         if level:
173             if level <= current_output_verbose_level and not self.silentSysStd:
174                 sys.stdout.write(message)
175         else:
176             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
177                 sys.stdout.write(message)
178         self.flush()
179
180     def error(self, message):
181         '''Print an error.
182         
183         :param message str: The message to print.
184         '''
185         # Print in the log file
186         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
187
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))
192         else:
193             sys.stderr.write(_('ERROR:') + message)
194
195     def flush(self):
196         '''Flush terminal
197         '''
198         sys.stdout.flush()
199         self.logTxtFile.flush()
200         
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
206         
207         :param attribute dict: the attribute to add to the node "Site".
208         '''       
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))
213         tf = dt
214         delta = tf - t0
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)})
225         
226         # Add the attribute passed to the method
227         self.xmlFile.append_node_attrib("Site", attrib=attribute)
228         
229         # Call the method to write the xml file on the hard drive
230         self.xmlFile.write_tree(stylesheet = "command.xsl")
231         
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 
235                                 + "_" 
236                                 + self.config.VARS.command 
237                                 + ".pyconf")
238         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
239         f = open(dumpedPyconfFilePath, 'w')
240         self.config.__save__(f)
241         f.close()
242         
243
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
247     
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)
251     '''
252     Y = date[:4]
253     m = date[4:6]
254     dd = date[6:8]
255     H = date[9:11]
256     M = date[11:13]
257     S = date[13:15]
258     return Y, m, dd, H, M, S
259
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
263     
264     :param timedelta datetime.timedelta: The delta between two dates
265     :return: The number of seconds corresponding to timedelta.
266     :rtype: float
267     '''
268     return (
269         timedelta.microseconds + 0.0 +
270         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
271         
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.
275     
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
282     
283     :return: True if cmd is not in notShownCommands and the application 
284              in the log file corresponds to application
285     :rtype: boolean
286     '''
287     # When the command is not in notShownCommands, no need to go further :
288     # Do not show
289     if cmd in notShownCommands:
290         return False, None, None
291  
292     # Get the application of the log file
293     try:
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
299
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
308         
309         return True, appliLog, launched_cmd
310     
311     if application == 'None':
312             return True, None, None
313         
314     return False, None, None
315
316 def list_log_file(dirPath, expression):
317     '''Find all files corresponding to expression in dirPath
318     
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
322     :rtype: list
323     '''
324     lRes = []
325     for fileName in os.listdir(dirPath):
326         # YYYYMMDD_HHMMSS_namecmd.xml
327         sExpr = expression
328         oExpr = re.compile(sExpr)
329         if oExpr.search(fileName):
330             file_name = 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')]
345                 host = ""
346             else:
347                 cmd = date_hour_cmd_host[2]
348                 host = date_hour_cmd_host[3][:-len('.xml')]
349             lRes.append((os.path.join(dirPath, fileName), 
350                          date_not_formated,
351                          date,
352                          hour_not_formated,
353                          hour,
354                          cmd,
355                          host))
356     return lRes
357
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
361     
362     :param logDir str: the directory to parse
363     :param application str: the name of the application if there is any
364     '''
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:
376         if showLog:
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, 
381                                              "hour" : hour, 
382                                              "cmd" : cmd, 
383                                              "application" : cmdAppli,
384                                              "full_command" : full_cmd})
385     
386     # Write the file on the hard drive
387     xmlHat.write_tree('hat.xsl')