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