Salome HOME
bug fix: sat generate
[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 logCommandFileExpression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$"
31
32 class Logger(object):
33     '''Class to handle log mechanism.
34     '''
35     def __init__(self,
36                  config,
37                  silent_sysstd=False,
38                  all_in_terminal=False,
39                  micro_command = False):
40         '''Initialization
41         
42         :param config pyconf.Config: The global configuration.
43         :param silent_sysstd boolean: if True, do not write anything
44                                       in terminal.
45         '''
46         self.config = config
47         self.default_level = 3
48         self.silentSysStd = silent_sysstd
49         
50         # Construct xml log file location for sat prints.
51         prefix = ""
52         if micro_command:
53             prefix = "micro_"
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)
61         
62         src.ensure_path_exists(os.path.dirname(logFilePath))
63         src.ensure_path_exists(os.path.dirname(txtFilePath))
64         
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
71         
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]
75         
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
85         if all_in_terminal:
86             self.logTxtFile = sys.__stdout__
87         
88     def put_initial_xml_fields(self):
89         '''Method called at class initialization : Put all fields 
90            corresponding to the command context (user, time, ...)
91         '''
92         # command name
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" : 
111                                                         date_hour})
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
123         # command 
124         self.xmlFile.add_simple_node("Links")
125
126     def add_link(self,
127                  log_file_name,
128                  command_name,
129                  command_res,
130                  full_launched_command):
131         '''Add a link to another log file.
132         
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 
137                                           ("sat command ...")
138         '''
139         xmlLinks = self.xmlFile.xmlroot.find("Links")
140         src.xmlManager.add_simple_node(xmlLinks,
141                                        "link", 
142                                        text = log_file_name,
143                                        attrib = {"command" : command_name,
144                                                  "passed" : command_res,
145                                            "launchedCommand" : full_launched_command})
146
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.
150         
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.
155         '''
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))
160
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)
167         
168         # Print message regarding the output level value
169         if level:
170             if level <= current_output_verbose_level and not self.silentSysStd:
171                 sys.stdout.write(message)
172         else:
173             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
174                 sys.stdout.write(message)
175         self.flush()
176
177     def error(self, message):
178         '''Print an error.
179         
180         :param message str: The message to print.
181         '''
182         # Print in the log file
183         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
184
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))
189         else:
190             sys.stderr.write(_('ERROR:') + message)
191
192     def flush(self):
193         '''Flush terminal
194         '''
195         sys.stdout.flush()
196         self.logTxtFile.flush()
197         
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
203         
204         :param attribute dict: the attribute to add to the node "Site".
205         '''       
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))
210         tf = dt
211         delta = tf - t0
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)})
222         
223         # Add the attribute passed to the method
224         self.xmlFile.append_node_attrib("Site", attrib=attribute)
225         
226         # Call the method to write the xml file on the hard drive
227         self.xmlFile.write_tree(stylesheet = "command.xsl")
228         
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 
232                                 + "_" 
233                                 + self.config.VARS.command 
234                                 + ".pyconf")
235         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
236         f = open(dumpedPyconfFilePath, 'w')
237         self.config.__save__(f)
238         f.close()
239         
240
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
244     
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)
248     '''
249     Y = date[:4]
250     m = date[4:6]
251     dd = date[6:8]
252     H = date[9:11]
253     M = date[11:13]
254     S = date[13:15]
255     return Y, m, dd, H, M, S
256
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
260     
261     :param timedelta datetime.timedelta: The delta between two dates
262     :return: The number of seconds corresponding to timedelta.
263     :rtype: float
264     '''
265     return (
266         timedelta.microseconds + 0.0 +
267         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
268         
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.
272     
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
279     
280     :return: True if cmd is not in notShownCommands and the application 
281              in the log file corresponds to application
282     :rtype: boolean
283     '''
284     # When the command is not in notShownCommands, no need to go further :
285     # Do not show
286     if cmd in notShownCommands:
287         return False, None
288  
289     # Get the application of the log file
290     try:
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)))
295         return False, None
296
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
304         
305         return True, appliLog
306     
307     if application == 'None':
308             return True, None    
309         
310     return False, None
311
312 def list_log_file(dirPath, expression):
313     '''Find all files corresponding to expression in dirPath
314     
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
318     :rtype: list
319     '''
320     lRes = []
321     for fileName in os.listdir(dirPath):
322         # YYYYMMDD_HHMMSS_namecmd.xml
323         sExpr = expression
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))
339     return lRes
340
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
344     
345     :param logDir str: the directory to parse
346     :param application str: the name of the application if there is any
347     '''
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:
359         if showLog:
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, 
364                                              "hour" : hour, 
365                                              "cmd" : cmd, 
366                                              "application" : cmdAppli})
367     
368     # Write the file on the hard drive
369     xmlHat.write_tree('hat.xsl')