]> SALOME platform Git repositories - tools/sat.git/blob - src/logger.py
Salome HOME
merge cvw/sprint_180319 for doc alabaster
[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
19 """\
20 Implements the classes and method relative to the logging
21 """
22
23 import sys
24 import os
25 import datetime
26 import re
27 import tempfile
28
29 import src
30 from . import printcolors
31 from . import xmlManager
32
33 log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
34 log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$"
35
36 class Logger(object):
37     """\
38     Class to handle log mechanism.
39     """
40     def __init__(self,
41                  config,
42                  silent_sysstd=False,
43                  all_in_terminal=False,
44                  micro_command = False):
45         """Initialization
46         
47         :param config pyconf.Config: The global configuration.
48         :param silent_sysstd boolean: if True, do not write anything
49                                       in terminal.
50         """
51         self.config = config
52         self.default_level = 3
53         self.silentSysStd = silent_sysstd
54         
55         # Construct xml log file location for sat prints.
56         prefix = ""
57         if micro_command:
58             prefix = "micro_"
59         hour_command_host = (config.VARS.datehour + "_" + 
60                              config.VARS.command + "_" + 
61                              config.VARS.hostname)
62         logFileName = prefix + hour_command_host + ".xml"
63         log_dir = src.get_log_path(config)
64         logFilePath = os.path.join(log_dir, logFileName)
65         # Construct txt file location in order to log 
66         # the external commands calls (cmake, make, git clone, etc...)
67         txtFileName = prefix + hour_command_host + ".txt"
68         txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
69         
70         src.ensure_path_exists(os.path.dirname(logFilePath))
71         src.ensure_path_exists(os.path.dirname(txtFilePath))
72         
73         # The path of the log files (one for sat traces, and the other for 
74         # the system commands traces)
75         self.logFileName = logFileName
76         self.logFilePath = logFilePath
77         self.txtFileName = txtFileName
78         self.txtFilePath = txtFilePath
79         
80         # The list of all log files corresponding to the current command and
81         # the commands called by the current command
82         self.l_logFiles = [logFilePath, txtFilePath]
83         
84         # Initialize xml instance and put first fields 
85         # like beginTime, user, command, etc... 
86         self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", 
87                             attrib = {"application" : config.VARS.application})
88         self.put_initial_xml_fields()
89         # Initialize the txt file for reading
90         try:
91             self.logTxtFile = open(str(self.txtFilePath), 'w')
92         except IOError:
93             #msg1 = _("WARNING! Trying to write to a file that"
94             #         " is not accessible:")
95             #msg2 = _("The logs won't be written.")
96             #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1),
97             #                        src.printcolors.printcLabel(str(self.txtFilePath)),
98             #                        src.printcolors.printcWarning(msg2) ))
99             self.logTxtFile = tempfile.TemporaryFile()
100             
101         # If the option all_in_terminal was called, all the system commands
102         # are redirected to the terminal
103         if all_in_terminal:
104             self.logTxtFile = sys.__stdout__
105         
106     def put_initial_xml_fields(self):
107         """\
108         Called at class initialization: Put all fields 
109         corresponding to the command context (user, time, ...)
110         """
111         # command name
112         self.xmlFile.add_simple_node("Site", attrib={"command" : 
113                                                      self.config.VARS.command})
114         # version of salomeTools
115         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : 
116                                             self.config.INTERNAL.sat_version})
117         # machine name on which the command has been launched
118         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : 
119                                                     self.config.VARS.hostname})
120         # Distribution of the machine
121         self.xmlFile.append_node_attrib("Site", attrib={"OS" : 
122                                                         self.config.VARS.dist})
123         # The user that have launched the command
124         self.xmlFile.append_node_attrib("Site", attrib={"user" : 
125                                                         self.config.VARS.user})
126         # The time when command was launched
127         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
128         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
129         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : 
130                                                         date_hour})
131         # The application if any
132         if "APPLICATION" in self.config:
133             self.xmlFile.append_node_attrib("Site", 
134                         attrib={"application" : self.config.VARS.application})
135         # The initialization of the trace node
136         self.xmlFile.add_simple_node("Log",text="")
137         # The system commands logs
138         self.xmlFile.add_simple_node("OutLog",
139                                     text=os.path.join("OUT", self.txtFileName))
140         # The initialization of the node where 
141         # to put the links to the other sat commands that can be called by any
142         # command 
143         self.xmlFile.add_simple_node("Links")
144
145     def add_link(self,
146                  log_file_name,
147                  command_name,
148                  command_res,
149                  full_launched_command):
150         """Add a link to another log file.
151         
152         :param log_file_name str: The file name of the link.
153         :param command_name str: The name of the command linked.
154         :param command_res str: The result of the command linked. "0" or "1"
155         :parma full_launched_command str: The full lanch command 
156                                           ("sat command ...")
157         """
158         xmlLinks = self.xmlFile.xmlroot.find("Links")
159         src.xmlManager.add_simple_node(xmlLinks,
160                                        "link", 
161                                        text = log_file_name,
162                                        attrib = {"command" : command_name,
163                                                  "passed" : command_res,
164                                            "launchedCommand" : full_launched_command})
165
166     def write(self, message, level=None, screenOnly=False):
167         """\
168         function used in the commands 
169         to print in the terminal and the log file.
170         
171         :param message str: The message to print.
172         :param level int: The output level corresponding 
173                           to the message 0 < level < 6.
174         :param screenOnly boolean: if True, do not write in log file.
175         """
176         # do not write message starting with \r to log file
177         if not message.startswith("\r") and not screenOnly:
178             self.xmlFile.append_node_text("Log", 
179                                           printcolors.cleancolor(message))
180
181         # get user or option output level
182         current_output_verbose_level = self.config.USER.output_verbose_level
183         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
184             # clean the message color if the terminal is redirected by user
185             # ex: sat compile appli > log.txt
186             message = printcolors.cleancolor(message)
187         
188         # Print message regarding the output level value
189         if level:
190             if level <= current_output_verbose_level and not self.silentSysStd:
191                 sys.stdout.write(message)
192         else:
193             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
194                 sys.stdout.write(message)
195         self.flush()
196
197     def error(self, message):
198         """Print an error.
199         
200         :param message str: The message to print.
201         """
202         # Print in the log file
203         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
204
205         # Print in the terminal and clean colors if the terminal 
206         # is redirected by user
207         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
208             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
209         else:
210             sys.stderr.write(_('ERROR:') + message)
211
212     def flush(self):
213         """Flush terminal"""
214         sys.stdout.flush()
215         self.logTxtFile.flush()
216         
217     def end_write(self, attribute):
218         """\
219         Called just after command end: Put all fields 
220         corresponding to the command end context (time).
221         Write the log xml file on the hard drive.
222         And display the command to launch to get the log
223         
224         :param attribute dict: the attribute to add to the node "Site".
225         """       
226         # Get current time (end of command) and format it
227         dt = datetime.datetime.now()
228         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
229         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
230         tf = dt
231         delta = tf - t0
232         total_time = timedelta_total_seconds(delta)
233         hours = int(total_time / 3600)
234         minutes = int((total_time - hours*3600) / 60)
235         seconds = total_time - hours*3600 - minutes*60
236         # Add the fields corresponding to the end time
237         # and the total time of command
238         endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss')
239         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
240         self.xmlFile.append_node_attrib("Site", 
241                 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
242         
243         # Add the attribute passed to the method
244         self.xmlFile.append_node_attrib("Site", attrib=attribute)
245         
246         # Call the method to write the xml file on the hard drive
247         self.xmlFile.write_tree(stylesheet = "command.xsl")
248         
249         # Dump the config in a pyconf file in the log directory
250         logDir = src.get_log_path(self.config)
251         dumpedPyconfFileName = (self.config.VARS.datehour 
252                                 + "_" 
253                                 + self.config.VARS.command 
254                                 + ".pyconf")
255         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
256         try:
257             f = open(dumpedPyconfFilePath, 'w')
258             self.config.__save__(f)
259             f.close()
260         except IOError:
261             pass
262
263 def date_to_datetime(date):
264     """\
265     From a string date in format YYYYMMDD_HHMMSS
266     returns list year, mon, day, hour, minutes, seconds 
267     
268     :param date str: The date in format YYYYMMDD_HHMMSS
269     :return: the same date and time in separate variables.
270     :rtype: (str,str,str,str,str,str)
271     """
272     Y = date[:4]
273     m = date[4:6]
274     dd = date[6:8]
275     H = date[9:11]
276     M = date[11:13]
277     S = date[13:15]
278     return Y, m, dd, H, M, S
279
280 def timedelta_total_seconds(timedelta):
281     """\
282     Replace total_seconds from datetime module 
283     in order to be compatible with old python versions
284     
285     :param timedelta datetime.timedelta: The delta between two dates
286     :return: The number of seconds corresponding to timedelta.
287     :rtype: float
288     """
289     return (
290         timedelta.microseconds + 0.0 +
291         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
292         
293 def show_command_log(logFilePath, cmd, application, notShownCommands):
294     """\
295     Used in updateHatXml. 
296     Determine if the log xml file logFilePath 
297     has to be shown or not in the hat log.
298     
299     :param logFilePath str: the path to the command xml log file
300     :param cmd str: the command of the log file
301     :param application str: the application passed as parameter 
302                             to the salomeTools command
303     :param notShownCommands list: the list of commands 
304                                   that are not shown by default
305     
306     :return: True if cmd is not in notShownCommands and the application 
307              in the log file corresponds to application
308     :rtype: boolean
309     """
310     # When the command is not in notShownCommands, no need to go further :
311     # Do not show
312     if cmd in notShownCommands:
313         return False, None, None
314  
315     # Get the application of the log file
316     try:
317         logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
318     except Exception as e:
319         msg = _("WARNING: the log file %s cannot be read:" % logFilePath)
320         sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e)))
321         return False, None, None
322
323     if 'application' in logFileXml.xmlroot.keys():
324         appliLog = logFileXml.xmlroot.get('application')
325         launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand']
326         # if it corresponds, then the log has to be shown
327         if appliLog == application:
328             return True, appliLog, launched_cmd
329         elif application != 'None':
330             return False, appliLog, launched_cmd
331         
332         return True, appliLog, launched_cmd
333     
334     if application == 'None':
335             return True, None, None
336         
337     return False, None, None
338
339 def list_log_file(dirPath, expression):
340     """Find all files corresponding to expression in dirPath
341     
342     :param dirPath str: the directory where to search the files
343     :param expression str: the regular expression of files to find
344     :return: the list of files path and informations about it
345     :rtype: list
346     """
347     lRes = []
348     for fileName in os.listdir(dirPath):
349         # YYYYMMDD_HHMMSS_namecmd.xml
350         sExpr = expression
351         oExpr = re.compile(sExpr)
352         if oExpr.search(fileName):
353             file_name = fileName
354             if fileName.startswith("micro_"):
355                 file_name = fileName[len("micro_"):]
356             # get date and hour and format it
357             date_hour_cmd_host = file_name.split('_')
358             date_not_formated = date_hour_cmd_host[0]
359             date = "%s/%s/%s" % (date_not_formated[6:8], 
360                                  date_not_formated[4:6], 
361                                  date_not_formated[0:4])
362             hour_not_formated = date_hour_cmd_host[1]
363             hour = "%s:%s:%s" % (hour_not_formated[0:2], 
364                                  hour_not_formated[2:4], 
365                                  hour_not_formated[4:6])
366             if len(date_hour_cmd_host) < 4:
367                 cmd = date_hour_cmd_host[2][:-len('.xml')]
368                 host = ""
369             else:
370                 cmd = date_hour_cmd_host[2]
371                 host = date_hour_cmd_host[3][:-len('.xml')]
372             lRes.append((os.path.join(dirPath, fileName), 
373                          date_not_formated,
374                          date,
375                          hour_not_formated,
376                          hour,
377                          cmd,
378                          host))
379     return lRes
380
381 def update_hat_xml(logDir, application=None, notShownCommands = []):
382     """\
383     Create the xml file in logDir that contain all the xml file 
384     and have a name like YYYYMMDD_HHMMSS_namecmd.xml
385     
386     :param logDir str: the directory to parse
387     :param application str: the name of the application if there is any
388     """
389     # Create an instance of XmlLogFile class to create hat.xml file
390     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
391     xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
392                                     "LOGlist", {"application" : application})
393     # parse the log directory to find all the command logs, 
394     # then add it to the xml file
395     lLogFile = list_log_file(logDir, log_macro_command_file_expression)
396     for filePath, __, date, __, hour, cmd, __ in lLogFile:
397         showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd,
398                                               application, notShownCommands)
399         #if cmd not in notShownCommands:
400         if showLog:
401             # add a node to the hat.xml file
402             xmlHat.add_simple_node("LogCommand", 
403                                    text=os.path.basename(filePath), 
404                                    attrib = {"date" : date, 
405                                              "hour" : hour, 
406                                              "cmd" : cmd, 
407                                              "application" : cmdAppli,
408                                              "full_command" : full_cmd})
409     
410     # Write the file on the hard drive
411     xmlHat.write_tree('hat.xsl')