Salome HOME
add the jobs command
[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, config, silent_sysstd=False, all_in_terminal=False):
36         '''Initialization
37         
38         :param config pyconf.Config: The global configuration.
39         :param silent_sysstd boolean: if True, do not write anything
40                                       in terminal.
41         '''
42         self.config = config
43         self.default_level = 3
44         self.silentSysStd = silent_sysstd
45         
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)
53         
54         src.ensure_path_exists(os.path.dirname(logFilePath))
55         src.ensure_path_exists(os.path.dirname(txtFilePath))
56         
57         self.logFileName = logFileName
58         self.logFilePath = logFilePath
59         self.txtFileName = txtFileName
60         self.txtFilePath = txtFilePath
61         # Initialize xml instance and put first fields 
62         # like beginTime, user, command, etc... 
63         self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", 
64                             attrib = {"application" : config.VARS.application})
65         self.put_initial_xml_fields()
66         # Initialize the txt file for reading
67         self.logTxtFile = open(str(self.txtFilePath), 'w')
68         if all_in_terminal:
69             self.logTxtFile = sys.__stdout__
70         
71     def put_initial_xml_fields(self):
72         '''Method called at class initialization : Put all fields 
73            corresponding to the command context (user, time, ...)
74         '''
75         # command name
76         self.xmlFile.add_simple_node("Site", attrib={"command" : 
77                                                      self.config.VARS.command})
78         # version of salomeTools
79         self.xmlFile.append_node_attrib("Site", attrib={"satversion" : 
80                                             self.config.INTERNAL.sat_version})
81         # machine name on which the command has been launched
82         self.xmlFile.append_node_attrib("Site", attrib={"hostname" : 
83                                                     self.config.VARS.hostname})
84         # Distribution of the machine
85         self.xmlFile.append_node_attrib("Site", attrib={"OS" : 
86                                                         self.config.VARS.dist})
87         # The user that have launched the command
88         self.xmlFile.append_node_attrib("Site", attrib={"user" : 
89                                                         self.config.VARS.user})
90         # The time when command was launched
91         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
92         date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S)
93         self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : 
94                                                         date_hour})
95         # The application if any
96         if "APPLICATION" in self.config:
97             self.xmlFile.append_node_attrib("Site", 
98                         attrib={"application" : self.config.VARS.application})
99         # The initialization of the trace node
100         self.xmlFile.add_simple_node("Log",text="")
101         # The system commands logs
102         self.xmlFile.add_simple_node("OutLog",
103                                     text=os.path.join("OUT", self.txtFileName))
104         # The initialization of the node where 
105         # to put the links to the other sat commands that can be called by any
106         # command 
107         self.xmlFile.add_simple_node("Links")
108
109     def write(self, message, level=None, screenOnly=False):
110         '''the function used in the commands 
111         that will print in the terminal and the log file.
112         
113         :param message str: The message to print.
114         :param level int: The output level corresponding 
115                           to the message 0 < level < 6.
116         :param screenOnly boolean: if True, do not write in log file.
117         '''
118         # do not write message starting with \r to log file
119         if not message.startswith("\r") and not screenOnly:
120             self.xmlFile.append_node_text("Log", 
121                                           printcolors.cleancolor(message))
122
123         # get user or option output level
124         current_output_verbose_level = self.config.USER.output_verbose_level
125         if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()):
126             # clean the message color if the terminal is redirected by user
127             # ex: sat compile appli > log.txt
128             message = printcolors.cleancolor(message)
129         
130         # Print message regarding the output level value
131         if level:
132             if level <= current_output_verbose_level and not self.silentSysStd:
133                 sys.stdout.write(message)
134         else:
135             if self.default_level <= current_output_verbose_level and not self.silentSysStd:
136                 sys.stdout.write(message)
137
138     def error(self, message):
139         '''Print an error.
140         
141         :param message str: The message to print.
142         '''
143         # Print in the log file
144         self.xmlFile.append_node_text("traces", _('ERROR:') + message)
145
146         # Print in the terminal and clean colors if the terminal 
147         # is redirected by user
148         if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()):
149             sys.stderr.write(printcolors.printcError(_('ERROR:') + message))
150         else:
151             sys.stderr.write(_('ERROR:') + message)
152
153     def flush(self):
154         '''Flush terminal
155         '''
156         sys.stdout.flush()
157         self.logTxtFile.flush()
158         
159     def end_write(self, attribute):
160         '''Method called just after command end : Put all fields 
161            corresponding to the command end context (time).
162            Write the log xml file on the hard drive.
163            And display the command to launch to get the log
164         
165         :param attribute dict: the attribute to add to the node "Site".
166         '''       
167         # Get current time (end of command) and format it
168         dt = datetime.datetime.now()
169         Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour)
170         t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S))
171         tf = dt
172         delta = tf - t0
173         total_time = timedelta_total_seconds(delta)
174         hours = int(total_time / 3600)
175         minutes = int((total_time - hours*3600) / 60)
176         seconds = total_time - hours*3600 - minutes*60
177         # Add the fields corresponding to the end time
178         # and the total time of command
179         endtime = dt.strftime('%d/%Y/%m %Hh%Mm%Ss')
180         self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime})
181         self.xmlFile.append_node_attrib("Site", 
182                 attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)})
183         
184         # Add the attribute passed to the method
185         self.xmlFile.append_node_attrib("Site", attrib=attribute)
186         
187         # Call the method to write the xml file on the hard drive
188         self.xmlFile.write_tree(stylesheet = "command.xsl")
189         
190         # Dump the config in a pyconf file in the log directory
191         logDir = self.config.SITE.log.log_dir
192         dumpedPyconfFileName = (self.config.VARS.datehour 
193                                 + "_" 
194                                 + self.config.VARS.command 
195                                 + ".pyconf")
196         dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName)
197         f = open(dumpedPyconfFilePath, 'w')
198         self.config.__save__(f)
199         f.close()
200         
201
202 def date_to_datetime(date):
203     '''Little method that gets year, mon, day, hour , 
204        minutes and seconds from a str in format YYYYMMDD_HHMMSS
205     
206     :param date str: The date in format YYYYMMDD_HHMMSS
207     :return: the same date and time in separate variables.
208     :rtype: (str,str,str,str,str,str)
209     '''
210     Y = date[:4]
211     m = date[4:6]
212     dd = date[6:8]
213     H = date[9:11]
214     M = date[11:13]
215     S = date[13:15]
216     return Y, m, dd, H, M, S
217
218 def timedelta_total_seconds(timedelta):
219     '''Little method to replace total_seconds from 
220        datetime module in order to be compatible with old python versions
221     
222     :param timedelta datetime.timedelta: The delta between two dates
223     :return: The number of seconds corresponding to timedelta.
224     :rtype: float
225     '''
226     return (
227         timedelta.microseconds + 0.0 +
228         (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
229         
230 def show_command_log(logFilePath, cmd, application, notShownCommands):
231     '''Used in updateHatXml. Determine if the log xml file logFilePath 
232        has to be shown or not in the hat log.
233     
234     :param logFilePath str: the path to the command xml log file
235     :param cmd str: the command of the log file
236     :param application str: the application passed as parameter 
237                             to the salomeTools command
238     :param notShownCommands list: the list of commands 
239                                   that are not shown by default
240     
241     :return: True if cmd is not in notShownCommands and the application 
242              in the log file corresponds to application
243     :rtype: boolean
244     '''
245     # When the command is not in notShownCommands, no need to go further :
246     # Do not show
247     if cmd in notShownCommands:
248         return False, None
249  
250     # Get the application of the log file
251     logFileXml = src.xmlManager.ReadXmlFile(logFilePath)
252     if 'application' in logFileXml.xmlroot.keys():
253         appliLog = logFileXml.xmlroot.get('application')
254         # if it corresponds, then the log has to be shown
255         if appliLog == application:
256             return True, appliLog
257         elif application != 'None':
258             return False, appliLog
259         
260         return True, appliLog
261     
262     if application == 'None':
263             return True, None    
264         
265     return False, None
266
267 def list_log_file(dirPath, expression):
268     '''Find all files corresponding to expression in dirPath
269     
270     :param dirPath str: the directory where to search the files
271     :param expression str: the regular expression of files to find
272     :return: the list of files path and informations about it
273     :rtype: list
274     '''
275     lRes = []
276     for fileName in os.listdir(dirPath):
277         # YYYYMMDD_HHMMSS_namecmd.xml
278         sExpr = expression
279         oExpr = re.compile(sExpr)
280         if oExpr.search(fileName):
281             # get date and hour and format it
282             date_hour_cmd = fileName.split('_')
283             date_not_formated = date_hour_cmd[0]
284             date = "%s/%s/%s" % (date_not_formated[6:8], 
285                                  date_not_formated[4:6], date_not_formated[0:4] )
286             hour_not_formated = date_hour_cmd[1]
287             hour = "%s:%s:%s" % (hour_not_formated[0:2], 
288                                  hour_not_formated[2:4], hour_not_formated[4:6])
289             cmd = date_hour_cmd[2][:-len('.xml')]
290             lRes.append((os.path.join(dirPath, fileName), 
291                          date_not_formated, date, hour_not_formated, hour, cmd))
292     return lRes
293
294 def update_hat_xml(logDir, application=None, notShownCommands = []):
295     '''Create the xml file in logDir that contain all the xml file 
296        and have a name like YYYYMMDD_HHMMSS_namecmd.xml
297     
298     :param logDir str: the directory to parse
299     :param application str: the name of the application if there is any
300     '''
301     # Create an instance of XmlLogFile class to create hat.xml file
302     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
303     xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
304                                     "LOGlist", {"application" : application})
305     # parse the log directory to find all the command logs, 
306     # then add it to the xml file
307     lLogFile = list_log_file(logDir, logCommandFileExpression)
308     for filePath, _, date, _, hour, cmd in lLogFile:
309         showLog, cmdAppli = show_command_log(filePath, cmd,
310                                               application, notShownCommands)
311         #if cmd not in notShownCommands:
312         if showLog:
313             # add a node to the hat.xml file
314             xmlHat.add_simple_node("LogCommand", 
315                                    text=os.path.basename(filePath), 
316                                    attrib = {"date" : date, 
317                                              "hour" : hour, 
318                                              "cmd" : cmd, 
319                                              "application" : cmdAppli})
320     
321     # Write the file on the hard drive
322     xmlHat.write_tree('hat.xsl')