]> SALOME platform Git repositories - tools/sat.git/blob - src/logger.py
Salome HOME
48c6633573c17cc19ced3931c826a793f977e13c
[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 import tempfile
26
27 import src
28 from . import printcolors
29 from . import xmlManager
30
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$"
33
34 class Logger(object):
35     '''Class to handle log mechanism.
36     '''
37     def __init__(self,
38                  config,
39                  silent_sysstd=False,
40                  all_in_terminal=False,
41                  micro_command = False):
42         '''Initialization
43         
44         :param config pyconf.Config: The global configuration.
45         :param silent_sysstd boolean: if True, do not write anything
46                                       in terminal.
47         '''
48         self.config = config
49         self.default_level = 3
50         self.silentSysStd = silent_sysstd
51         
52         # Construct xml log file location for sat prints.
53         prefix = ""
54         if micro_command:
55             prefix = "micro_"
56         hour_command_host = (config.VARS.datehour + "_" + 
57                              config.VARS.command + "_" + 
58                              config.VARS.hostname)
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)
66         
67         src.ensure_path_exists(os.path.dirname(logFilePath))
68         src.ensure_path_exists(os.path.dirname(txtFilePath))
69         
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
76         
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]
80         
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
87         try:
88             self.logTxtFile = open(str(self.txtFilePath), 'w')
89         except IOError:
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()
97             
98         # If the option all_in_terminal was called, all the system commands
99         # are redirected to the terminal
100         if all_in_terminal:
101             self.logTxtFile = sys.__stdout__
102         
103     def put_initial_xml_fields(self):
104         '''Method called at class initialization : Put all fields 
105            corresponding to the command context (user, time, ...)
106         '''
107         # command name
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" : 
126                                                         date_hour})
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
138         # command 
139         self.xmlFile.add_simple_node("Links")
140
141     def add_link(self,
142                  log_file_name,
143                  command_name,
144                  command_res,
145                  full_launched_command):
146         '''Add a link to another log file.
147         
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 
152                                           ("sat command ...")
153         '''
154         xmlLinks = self.xmlFile.xmlroot.find("Links")
155         src.xmlManager.add_simple_node(xmlLinks,
156                                        "link", 
157                                        text = log_file_name,
158                                        attrib = {"command" : command_name,
159                                                  "passed" : command_res,
160                                            "launchedCommand" : full_launched_command})
161
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.
165         
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.
170         '''
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))
175
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)
182         
183         # Print message regarding the output level value
184         if level:
185             if level <= current_output_verbose_level and not self.silentSysStd:
186                 sys.stdout.write(message)
187         else:
188             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
189                 sys.stdout.write(message)
190         self.flush()
191
192     def error(self, message):
193         '''Print an error.
194         
195         :param message str: The message to print.
196         '''
197         # Print in the log file
198         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
199
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))
204         else:
205             sys.stderr.write(_('ERROR:') + message)
206
207     def flush(self):
208         '''Flush terminal
209         '''
210         sys.stdout.flush()
211         self.logTxtFile.flush()
212         
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
218         
219         :param attribute dict: the attribute to add to the node "Site".
220         '''       
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))
225         tf = dt
226         delta = tf - t0
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)})
237         
238         # Add the attribute passed to the method
239         self.xmlFile.append_node_attrib("Site", attrib=attribute)
240         
241         # Call the method to write the xml file on the hard drive
242         self.xmlFile.write_tree(stylesheet = "command.xsl")
243         
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 
247                                 + "_" 
248                                 + self.config.VARS.command 
249                                 + ".pyconf")
250         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
251         try:
252             f = open(dumpedPyconfFilePath, 'w')
253             self.config.__save__(f)
254             f.close()
255         except IOError:
256             pass
257
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
261     
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)
265     '''
266     Y = date[:4]
267     m = date[4:6]
268     dd = date[6:8]
269     H = date[9:11]
270     M = date[11:13]
271     S = date[13:15]
272     return Y, m, dd, H, M, S
273
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
277     
278     :param timedelta datetime.timedelta: The delta between two dates
279     :return: The number of seconds corresponding to timedelta.
280     :rtype: float
281     '''
282     return (
283         timedelta.microseconds + 0.0 +
284         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
285         
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.
289     
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
296     
297     :return: True if cmd is not in notShownCommands and the application 
298              in the log file corresponds to application
299     :rtype: boolean
300     '''
301     # When the command is not in notShownCommands, no need to go further :
302     # Do not show
303     if cmd in notShownCommands:
304         return False, None, None
305  
306     # Get the application of the log file
307     try:
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
313
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
322         
323         return True, appliLog, launched_cmd
324     
325     if application == 'None':
326             return True, None, None
327         
328     return False, None, None
329
330 def list_log_file(dirPath, expression):
331     '''Find all files corresponding to expression in dirPath
332     
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
336     :rtype: list
337     '''
338     lRes = []
339     for fileName in os.listdir(dirPath):
340         # YYYYMMDD_HHMMSS_namecmd.xml
341         sExpr = expression
342         oExpr = re.compile(sExpr)
343         if oExpr.search(fileName):
344             file_name = 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')]
359                 host = ""
360             else:
361                 cmd = date_hour_cmd_host[2]
362                 host = date_hour_cmd_host[3][:-len('.xml')]
363             lRes.append((os.path.join(dirPath, fileName), 
364                          date_not_formated,
365                          date,
366                          hour_not_formated,
367                          hour,
368                          cmd,
369                          host))
370     return lRes
371
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
375     
376     :param logDir str: the directory to parse
377     :param application str: the name of the application if there is any
378     '''
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:
390         if showLog:
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, 
395                                              "hour" : hour, 
396                                              "cmd" : cmd, 
397                                              "application" : cmdAppli,
398                                              "full_command" : full_cmd})
399     
400     # Write the file on the hard drive
401     xmlHat.write_tree('hat.xsl')